关于html:uniapp开发app做一个自定义可拖拽缩放编辑删除内容的海报

39次阅读

共计 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>

效果图:

正文完
 0