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

能在安卓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>

效果图:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理