共计 15438 个字符,预计需要花费 39 分钟才能阅读完成。
能在安卓 app 上应用动静增加文字、并且文字可能拖拽、编辑、删除、缩放、设置色彩、字体大小、文字排列形式,还有能够插入图片、并且图片也能缩放、删除。
假如场景:用户在后盾页面上配置自定义海报,须要将数据传到 app 并展现。
我应用的是 fabricjs。官网:http://fabricjs.com/
实现的原理就是应用 webview 间接实现,我在网上找了很久也没有找到比拟快的办法实现,其实有一种,那就是应用 canvas 去实现,然而 canvas 的绘制很简单、麻烦。大佬能够试试 emmm
间接上代码了 emmm,做个笔记。
index.vue 的代码如下:
<template> | |
<view class="promotionPosterEdit"> | |
<web-view :src="'../../../hybrid/html/promotionPosterEdit/index.html?data=' + obj"@message="getMessage"ref="wv"></web-view> | |
</view> | |
</template> | |
<script> | |
import {pathToBase64, base64ToPath} from '../../../js_sdk/mmmm-image-tools/index.js'; | |
export default {data() { | |
return { | |
obj: null, | |
currentWebview: null, | |
} | |
}, | |
onLoad() {let statusBarHeight = uni.getSystemInfoSync()['statusBarHeight'] | |
let top = uni.getStorageSync('navbarHeight') + 30 | |
let height = uni.getSystemInfoSync()['screenHeight'] - 64 - uni.getStorageSync('navbarHeight') - 74 | |
let width = uni.getSystemInfoSync()['screenWidth'] - 100 | |
let screenHeight = uni.getSystemInfoSync()['screenHeight'] | |
let obj = { | |
statusBarHeight: statusBarHeight, | |
top: top, | |
height: height, | |
width: width, | |
screenHeight: screenHeight | |
} | |
this.obj = JSON.stringify(obj) // 这里是传给 webview 的参数用来调整款式的 | |
}, | |
onReady() { | |
const self = this | |
self.currentWebview = self.$scope.$getAppWebview().children()[0] | |
}, | |
methods: {getMessage(e) {console.log('接管 html 发送的数据', e.detail) | |
if(e.detail.data[0].msg === 'openCamera') { // 关上手机相册 | |
uni.chooseImage({ | |
count: 1, | |
success: (res) => {console.log('长期门路', res.tempFilePaths[0]) | |
pathToBase64(res.tempFilePaths[0]).then(base64 => {let info = base64.replace(/[\r\n]/g, '') | |
const self = this; | |
self.currentWebview.evalJS(`uniEvent(${JSON.stringify(info)})`); //app 向 html 发送数据 | |
}).catch(error => {console.log('转换失败:', error); | |
}); | |
} | |
}) | |
} else if(e.detail.data[0].msg === 'save') { // 保留到手机相册 | |
console.log('app', e.detail.data[0].canvas) | |
uni.showLoading({title: '保留中...'}) | |
let imageStr = e.detail.data[0].canvas | |
base64ToPath(imageStr).then(path => {console.log('转换下载图片', path) | |
this.saveImage(path); | |
}).catch(error => {console.error('长期门路转换出错了:', error); | |
}); | |
} else if(e.detail.data[0].msg === 'wx') { // 转发到微信 | |
uni.showToast({ | |
icon: 'none', | |
title: '暂未凋谢' | |
}) | |
} else if(e.detail.data[0].msg === 'friend') { // 转发到微信朋友圈 | |
uni.showToast({ | |
icon: 'none', | |
title: '暂未凋谢' | |
}) | |
} else if(e.detail.data[0].msg === 'addEmptyText') { // 不能增加空文字 | |
uni.showToast({ | |
icon: 'none', | |
title: '请输出文字' | |
}) | |
} | |
}, | |
// 保留 canvas 图片到手机相册 | |
saveImage(filePath) { | |
uni.saveImageToPhotosAlbum({ | |
filePath, // 须要临时文件门路,base64 无奈保留 | |
success: () => {uni.hideLoading() | |
uni.showToast({ | |
icon: 'none', | |
title: '保留胜利' | |
}) | |
}, | |
fail: (error) => {console.error('保留失败,请重试', error); | |
} | |
}); | |
}, | |
} | |
} | |
</script> | |
<style lang="scss" scoped> | |
.promotionPosterEdit { | |
min-height: 100vh; | |
background-color: #303030; | |
} | |
</style> |
/hybrid/html/promotionPosterEdit/index.html 的代码如下:
应用的插件 html2canvas 1.4.1 https://html2canvas.hertzen.com
fabricjs5.3.0
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" | |
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> | |
<title> 网络网页 </title> | |
<link rel="stylesheet" href="./css/index.css" /> | |
</head> | |
<body> | |
<div class="post-message-section"> | |
<div class="headers"> | |
<div class="header"> | |
<div class="left item" data-action="navigateBack"> | |
<div class="back"> | |
<img src="../img/back.png" alt="back" /> | |
</div> | |
</div> | |
<div class="title item" style="color: #fff"> | |
<div class="txt"> 宣传海报编辑 </div> | |
</div> | |
<div class="right item"> | |
<div class="share"> | |
<img src="../img/share.png" alt="back" /> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="myCanvas"> | |
<canvas id="canvas"></canvas> | |
</div> | |
<div class="shares"> | |
<div class="sharesmask"></div> | |
<div class="content"> | |
<div class="itemsurl"> | |
<div class="item wx"> | |
<div class="imgs"> | |
<img src="../img/wechat.png" alt="wechat" srcset="" /> | |
</div> | |
<div class="txt"> 微信 </div> | |
</div> | |
<div class="item friends"> | |
<div class="imgs"> | |
<img src="../img/friends.png" alt="friends" srcset="" /> | |
</div> | |
<div class="txt"> 朋友圈 </div> | |
</div> | |
<!-- <div class="item"> | |
<div class="imgs"> | |
<img src="../../../static/download.png" alt="download" srcset="" /> | |
</div> | |
<div class="txt"> 生成海报 </div> | |
</div> --> | |
</div> | |
<div style="height: 6px;width: 100%;background-color: #ececec;"></div> | |
<div class="cancel"> 勾销 </div> | |
</div> | |
</div> | |
<div class="addText"> | |
<div class="addTextmask"></div> | |
<div class="addTextContent"> | |
<div class="title"> 文字款式设置 </div> | |
<div class="tabtool"> | |
<div class="items"> | |
<input class="color" type="color" value="#000000" /> | |
</div> | |
<div class="item bold"> | |
<img src="../img/bold.png" alt="bold" srcset="" /> | |
</div> | |
<div class="item italic"> | |
<img src="../img/italic.png" alt="italic" srcset="" /> | |
</div> | |
<div class="item amplify"> | |
<img src="../img/amplify.png" alt="amplify" srcset="" /> | |
</div> | |
<div class="item reduce"> | |
<img src="../img/reduce.png" alt="reduce" srcset="" /> | |
</div> | |
</div> | |
<div class="text"> | |
<textarea id="MainText" cols="30" row="10" style="font-size: 18px;" placeholder="请输出文字"></textarea> | |
</div> | |
<div class="confirm"> | |
<button class="cancelAddText btn btn-red" type="button"> 勾销 </button> | |
<button class="confirmAddText btn" type="button"> 确认 </button> | |
</div> | |
</div> | |
</div> | |
<div class="footer"> | |
<div class="itemBox"> | |
<div class="item reload"> | |
<div class="icons"> | |
<img src="../img/reload.png" alt="reload" /> | |
</div> | |
<div class="txt"> 复原默认 </div> | |
</div> | |
<div class="item pic"> | |
<div class="icons"> | |
<img src="../img/pic.png" alt="pic" /> | |
</div> | |
<div class="txt"> 图片 / 二维码 </div> | |
</div> | |
<div class="item edit-pen"> | |
<div class="icons"> | |
<img src="../img/edit-pen.png" alt="edit-pen" /> | |
</div> | |
<div class="txt"> 增加文字 </div> | |
</div> | |
<div class="item downs"> | |
<div class="icons"> | |
<img src="../img/downs.png" alt="downs" /> | |
</div> | |
<div class="txt"> 保留本地 </div> | |
</div> | |
</div> | |
</div> | |
<div class="deleteFooter"> | |
<div class="delete"> | |
<div class="box"> | |
<img src="../img/delete.png" alt="delete" srcset="" /> | |
<div class="tip"> 拖到此处删除 </div> | |
</div> | |
</div> | |
<div class="deleteactive"> | |
<div class="box"> | |
<img src="../img/deleteactive.png" alt="deleteactive" srcset="" /> | |
<div class="tip"> 松手即可删除 </div> | |
</div> | |
</div> | |
</div> | |
<input class="imageinput" style="display: none;" type="image" accept="image/jpeg,image/jpg,image/png" capture /> | |
</div> | |
<script src="../js/fabric.js"></script> | |
<script src="../js/html2canvas.min.js"></script> | |
<script type="text/javascript"> | |
var userAgent = navigator.userAgent; | |
if (/quickapp/i.test(userAgent)) { | |
// quickapp | |
document.write('<script type="text/javascript"src="https://quickapp/jssdk.webview.min.js"><\/script>'); | |
} | |
if (!/toutiaomicroapp/i.test(userAgent)) {document.querySelector('.post-message-section').style.visibility = 'visible'; | |
} | |
</script> | |
<!-- uni 的 SDK --> | |
<script src="../js/uni-webview.js"></script> | |
<!-- <script type="text/javascript" src="https://unpkg.com/@dcloudio/uni-webview-js@0.0.1/index.js"></script> --> | |
<script type="text/javascript"> | |
// console.log('接管参数', decodeURIComponent(location.href.split('data=')[1])) | |
let params = JSON.parse(decodeURIComponent(location.href.split('data=')[1])) | |
document.getElementsByClassName('headers')[0].style.paddingTop = params.statusBarHeight + 'px' | |
document.getElementsByClassName('myCanvas')[0].style.paddingTop = params.top + 30 + 'px' | |
document.getElementById('canvas').setAttribute('width', params.width + 'px') | |
document.getElementById('canvas').setAttribute('height', params.height + 'px') | |
document.getElementsByClassName('post-message-section')[0].style.height = params.screenHeight + 'px' | |
// 生成 canvas | |
var canvas = new fabric.Canvas('canvas'); | |
// ... 这里能够写 canvas 对象的一些配置,前面将会介绍 | |
// console.log('fabric.Textbox', fabric.Textbox) | |
// 如果 <canvas> 标签没设置宽高,能够通过 js 动静设置 | |
// canvas.setWidth(350) | |
// canvas.setHeight(200) | |
function initCanvas() { | |
// 读取图片地址,设置画布背景 | |
fabric.Image.fromURL('../img/10801920.png', (img) => { | |
img.set({ | |
// 通过 scale 来设置图片大小,这里设置和画布一样大 | |
scaleX: canvas.width / img.width, | |
scaleY: canvas.height / img.height, | |
}); | |
// 设置背景 | |
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); | |
canvas.renderAll();}, {crossOrigin: 'anonymous' // 加上这个是因为我须要将海报下载到手机相册,容许跨域,如果没设置是下载不了图片的}); | |
// 通过图片门路增加 | |
fabric.Image.fromURL('../img/10801920.png', (img) => { | |
img.set({ | |
scaleX: canvas.width / img.width / 2, | |
scaleY: canvas.height / img.height / 2, | |
hasControls: true, // 是否开启图层的控件 | |
}); | |
// 增加对象后, 如下图 | |
canvas.add(img); | |
// deleteDiv(img) | |
}, {crossOrigin: 'anonymous'}); | |
// IText 不可让文字竖排。textbox 能够让文字竖排,主动依据宽度填写文字 | |
const IText = new fabric.Textbox('这是一段初始默认文字', { | |
left: 50, | |
top: 50, | |
// width: 50, // 不设置宽度, 拉伸主动换行 | |
fontSize: 18, // 字体大小 | |
fontWeight: 600, // 字体粗细 | |
fill: '#aaaaff', // 字体色彩 | |
fontStyle: 'italic', // 斜体 | |
splitByGrapheme: true, //true 文本会实时依据宽度进行换行 | |
hasControls: true, // 元素操作控件显示 \ 即描边正方体 | |
// fontFamily: 'Delicious', // 设置字体 | |
// fontFamily: 'monospace', // 设置字体 | |
// fontFamily: 'fantasy', // 设置字体 | |
// stroke: 'green', // 描边色彩 | |
// strokeWidth: 3, // 描边宽度 | |
// borderColor: 'orange', | |
// editingBorderColor: 'orange' // 点击文字进入编辑状态时的边框色彩 | |
}, {crossOrigin: 'anonymous'}); | |
// 增加文字 | |
canvas.add(IText) | |
// deleteDiv(IText) | |
} | |
initCanvas() | |
// 删除元素操作 | |
function deleteDiv(target) {target['on']('moving', function(info) {document.getElementsByClassName('deleteFooter')[0].style.display = 'block' | |
if(info.pointer.y > params.height) {document.getElementsByClassName('delete')[0].style.display = 'none' | |
document.getElementsByClassName('deleteactive')[0].style.display = 'block' | |
} else {document.getElementsByClassName('delete')[0].style.display = 'block' | |
document.getElementsByClassName('deleteactive')[0].style.display = 'none' | |
} | |
}) | |
target['on']('mouseup', function(info) {if(info.pointer.y > params.height + 5) {canvas.remove(target) | |
} | |
document.getElementsByClassName('delete')[0].style.display = 'block' | |
document.getElementsByClassName('deleteactive')[0].style.display = 'none' | |
document.getElementsByClassName('deleteFooter')[0].style.display = 'none' | |
}) | |
} | |
// webview 页面加载实现 | |
document.addEventListener('UniAppJSBridgeReady', function() { | |
// webview 加载实现,显示页面 | |
document.getElementsByClassName('post-message-section')[0].style.display = 'block' | |
// 返回上一页 | |
document.querySelector('.left').addEventListener('click', function(evt) {uni.navigateBack() | |
}); | |
// 分享 | |
document.querySelector('.right').addEventListener('click', function(evt) {console.log(getComputedStyle(document.documentElement).getPropertyValue("--sat")) | |
console.log(getComputedStyle(document.documentElement).getPropertyValue("--sab")) | |
document.querySelector('.shares').style.display = 'block' | |
slideTimer(0, 'add', 1, 30) | |
}); | |
document.querySelector('.sharesmask').addEventListener('click', function(evt) {console.log('点击分享遮罩层', evt.target) | |
slideTimer(200, 'dec', 1, 30) | |
}) | |
// 勾销分享 | |
document.querySelector('.cancel').addEventListener('click', function(evt) {slideTimer(200, 'dec', 1, 30) | |
}) | |
// 高度动画 | |
function slideTimer(height, type, setup, times) { | |
let timer = null | |
let heights = height | |
let setups = setup | |
timer = setInterval(() => {if(type === 'add') {heights += 10 * setups} else {heights -= 10 * setups} | |
console.log(Number(document.querySelector('.content').style.height.replace('px', ''))) | |
document.querySelector('.content').style.height = heights + 'px' | |
setups++ | |
if (Number(heights) <= 0 || Number(document.querySelector('.content').style.height.replace('px', '')) >= 200) {clearInterval(timer) | |
if(type !== 'add') {document.querySelector('.shares').style.display = 'none' | |
} | |
heights = 0 | |
setups = 1 | |
} | |
}, times) | |
} | |
// 分享到微信 | |
document.querySelector('.wx').addEventListener('click', function(evt) {console.log('分享到微信') | |
uni.postMessage({ | |
data: {msg: 'wx'} | |
}); | |
}) | |
// 分享到朋友圈 | |
document.querySelector('.friends').addEventListener('click', function(evt) {console.log('分享到朋友圈') | |
uni.postMessage({ | |
data: {msg: 'friend'} | |
}); | |
}) | |
// 点击增加文字 | |
document.querySelector('.edit-pen').addEventListener('click', function(evt) {console.log('增加文字') | |
document.querySelector('.addText').style.display = 'block' | |
initAddText() | |
document.getElementsByClassName('color')[0].onchange = function(evt) {console.log('色彩扭转', evt.target.value) | |
document.getElementById('MainText').style.color = evt.target.value | |
} | |
}) | |
// 点击加粗文字 | |
document.querySelector('.bold').addEventListener('click', function(evt) {console.log('点击加粗文字') | |
if(document.getElementById('MainText').style.fontWeight === 'bold') {document.getElementById('MainText').style.fontWeight = 'normal' | |
} else {document.getElementById('MainText').style.fontWeight = 'bold' | |
} | |
}) | |
// 点击斜体文字 | |
document.querySelector('.italic').addEventListener('click', function(evt) {console.log('点击斜体文字') | |
if(document.getElementById('MainText').style.fontStyle === 'italic') {document.getElementById('MainText').style.fontStyle = 'normal' | |
} else {document.getElementById('MainText').style.fontStyle = 'italic' | |
} | |
}) | |
// 点击文字变大 | |
document.querySelector('.amplify').addEventListener('click', function(evt) {console.log('点击文字变大') | |
console.log(Number(document.getElementById('MainText').style.fontSize.replace('px', ''))) | |
console.log(document.getElementById('MainText').style.fontSize.replace('px', '')) | |
document.getElementById('MainText').style.fontSize = Number(document.getElementById('MainText').style.fontSize.replace('px', ''))+1 +'px' | |
}) | |
// 点击文字变小 | |
document.querySelector('.reduce').addEventListener('click', function(evt) {console.log('点击文字变小') | |
if(Number(document.getElementById('MainText').style.fontSize.replace('px', '')) === 12) {console.log('无奈再减了') | |
} else {document.getElementById('MainText').style.fontSize = Number(document.getElementById('MainText').style.fontSize.replace('px', ''))-1 +'px' | |
} | |
}) | |
// 确认增加文字到 canva 中 | |
document.getElementsByClassName('confirmAddText')[0].addEventListener('click', function(evt) {console.log('确认增加到 canva 中') | |
if(document.getElementById('MainText').value !== '') { | |
// IText 不可让文字竖排。textbox 能够让文字竖排,主动依据宽度填写文字 | |
const IText = new fabric.Textbox(document.getElementById('MainText').value, { | |
left: 50, | |
top: 50, | |
// width: 50, // 不设置宽度, 拉伸主动换行 | |
fontSize: Number(document.getElementById('MainText').style.fontSize.replace('px', '')), // 字体大小 | |
fontWeight: document.getElementById('MainText').style.fontWeight, // 字体粗细 | |
fill: document.getElementById('MainText').style.color, // 字体色彩 | |
fontStyle: document.getElementById('MainText').style.fontStyle, // 斜体 | |
splitByGrapheme: true, //true 文本会实时依据宽度进行换行 | |
hasControls: true, // 元素操作控件显示 \ 即描边正方体 | |
}, {crossOrigin: 'anonymous'}); | |
// 增加文字 | |
canvas.add(IText); | |
deleteDiv(IText) | |
initAddText() | |
document.querySelector('.addText').style.display = 'none' | |
} else { | |
uni.postMessage({ | |
data: {msg: 'addEmptyText'} | |
}); | |
} | |
}) | |
// 敞开增加文字 | |
document.querySelector('.addTextmask').addEventListener('click', function(evt) {initAddText() | |
document.querySelector('.addText').style.display = 'none' | |
}) | |
// 勾销增加文字 | |
document.getElementsByClassName('cancelAddText')[0].addEventListener('click', function(evt) {console.log('勾销增加文字') | |
initAddText() | |
document.querySelector('.addText').style.display = 'none' | |
}) | |
// 初始化增加文字 | |
function initAddText() {document.getElementById('MainText').style.fontWeight = 'normal' | |
document.getElementById('MainText').style.fontStyle = 'normal' | |
document.getElementById('MainText').style.fontSize = '18px' | |
document.getElementById('MainText').style.color = '#000000' | |
document.getElementById('MainText').value = ''document.getElementsByClassName('color')[0].value ='#000000' | |
} | |
// 复原默认 canvas | |
document.querySelector('.reload').addEventListener('click', function(evt) {console.log('复原默认') | |
canvas.clear(); | |
initCanvas()}) | |
// app 被动与 html 通信,用于增加图片到 canvas 中 | |
function addUniEvenPassthrough() {window.uniEvent = function(info) {console.log('获取完相册了', info) | |
fabric.Image.fromURL(info, (img) => { | |
img.set({ | |
scaleX: canvas.width / img.width / 2, | |
scaleY: canvas.height / img.height / 2, | |
hasControls: true, // 是否开启图层的控件 | |
}); | |
// 增加对象后, 如下图 | |
canvas.add(img); | |
deleteDiv(img) | |
}, {crossOrigin: 'anonymous'}); | |
} | |
} | |
addUniEvenPassthrough() | |
// 点击了图片 / 二维码 | |
document.querySelector('.pic').addEventListener('click', function(evt) {console.log('点击了图片 / 二维码') | |
uni.postMessage({ | |
data: {msg: 'openCamera'} | |
}); | |
}) | |
// 海报保留本地 | |
document.querySelector('.downs').addEventListener('click', function(evt) {console.log('保留本地') | |
if (document.getElementsByClassName('canvasimg')[0]) {document.getElementsByClassName('canvasimg')[0].remove()} | |
let dom = document.getElementById('canvas') | |
var img = new Image(); | |
img.crossOrigin = "anonymous"; // 要害、容许跨域 | |
var image = dom.toDataURL('image/png') | |
img.src = image; | |
img.setAttribute('style','position: absolute; top: 0px; left: -666px;width: 320px; height: 750px;') | |
img.setAttribute('class', 'canvasimg') | |
document.body.appendChild(img); | |
let canvasdom = document.getElementsByTagName('canvas')[0] | |
html2canvas(canvasdom, { | |
width: canvasdom.clientWidth, //dom 原始宽度 | |
height: canvasdom.clientHeight, | |
scrollY: 0, // html2canvas 默认绘制视图内的页面,须要把 scrollY,scrollX 设置为 0 | |
scrollX: 0, | |
useCORS: true, // 反对跨域 | |
scale: 2, // 设置生成图片的像素比例,默认是 1,如果生成的图片含糊的话能够开启该配置项。(设置为 1 无奈触发长按扫一扫)}).then((res) => {console.log(res) | |
uni.postMessage({ | |
data: { | |
msg: 'save', | |
canvas: res.toDataURL('image/png').replace(/[\r\n]/g, '') | |
} | |
}); | |
}).catch(err => {console.log('生成失败') | |
}) | |
}) | |
}); | |
</script> | |
</body> | |
</html> |
效果图:
正文完