能在安卓 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>
效果图: