能在安卓app上使用动态添加文字、并且文字能够拖拽、编辑、删除、缩放、设置颜色、字体大小、文字排列方式,还有可以插入图片、并且图片也能缩放、删除。
假设场景:用户在后台页面上配置自定义海报,需要将数据传到app并展示。
我使用的是fabricjs。官网:http://fabricjs.com/
实现的原理就是使用webview间接实现,我在网上找了很久也没有找到比较快的方法实现,其实有一种,那就是使用canvas去实现,但是canvas的绘制很复杂、麻烦。大佬可以试试emmm
直接上代码了emmm,做个笔记。
index.vue的代码如下:
使用的插件image-tools:https://ext.dcloud.net.cn/plugin?id=123
<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>
效果图:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。