前端js实现canves画布中拖拽、放大、缩小、旋转图片和文字,设置背景图片,导出

最近在研究canves,想实现一个可以在画布中操作上传的内容,不经意间发现了个插件Fabric.js。Fabric.js 是一个强大的H5 canvas框架,在原生canvas之上提供了交互式对象模型,通过简洁的api就可以在画布上进行丰富的操作。该框架是个开源项目,官网地址:Fabric.js Javascript Canvas Library     


image

安装

npm安装

npm install fabric --save

cmd安装

<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.min.js"></script>

<script src="https://unpkg.com/coco-modal/coco-modal.min.js"></script> 弹框插件

初始化

首先在html页面中写一个350 x 200的canvas标签, 这里不写宽高也行,后面可以通过js来设置宽高

<canvas id="canvas" width="350" height="200"></canvas>

初始化fabric的canvas对象,创建一个卡片(后面都用card表示画布对象)

constcard =newfabric.Canvas('canvas')// ...这里可以写canvas对象的一些配置,后面将会介绍

// 如果<canvas>标签没设置宽高,可以通过js动态设置

card.setWidth(350)card.setHeight(200)

设置画布和监听事件

// 在canvas对象初始化后,通过以下方式监听

        // 比如监听画布的图层编辑事件

        exportCanves.onclick = function(e) {

            // 导出当前画布信息

            const currState = card.toJSON(); // 导出的Json如下图

            console.log(currState)

            // 加载画布信息

            // card.loadFromJSON(lastState, () => {

            //     card.renderAll();

            // });

            const dataURL = card.toDataURL({

                format: 'jpeg', // jpeg或png

                quality: 1.8 // 图片质量,仅jpeg时可用

                // 截取指定位置和大小

                //left: 100,

                //top: 100,

                //width: 200,

                //height: 200

            });

            downloadIamge(dataURL, 'img')

        }

        card.on('object:modified', (e) => {

            console.log(e.target) // e.target为当前编辑的Object

            objectMoving(e)

            objectModified(e)

            // ...旋转,缩放,移动等编辑图层的操作都监听到

            // 所以如果有撤销/恢复的场景,这里可以保存编辑状态

        });

        // 设置画布背景颜色

        card.backgroundColor = 'white';

        // 或者

        // card.setBackgroundColor('blue');

        let selectedObj

        // 方式二

        card.on('selection:created', (e) => {

            // 选中图层事件触发时,动态更新赋值

            console.log(e.target)

            selectedObj = e.target

            // 顺时针90°旋转

            const currAngle = selectedObj.angle; // 当前图层的角度

            const angle = currAngle === 360 ? 90 : currAngle + 90;

            // selectedObj.rotate(angle);

            // 如果是通过滑块的方式控制旋转

            // selectedObj.rotate(slideValue);

            // 水平翻转,同理垂直翻转改为scaleY属性

            // selectedObj.set({

            //     scaleX: -selectedObj.scaleX,

            // })

            //移除图层

            // card.remove(selectedObj) // 传入需要移除的object

            // 所有图层的操作之后,都需要调用这个方法

            card.renderAll()

        })

操作画布中的图层和文字

当对象移动时 限制对象的 不超出画布,当画布对象放大时限制其操出边界

<div class="button-box">

            <div class="upload-box">

                <button class="button"><label for="addBgImg">添加背景图片</label></button>

                <input style="display: none;" class="button" type="file" accept="image/*" name="uploader-input" class="uploader-file" id="addBgImg">

            </div>

            <div class="upload-box">

                <button class="button"><label for="addimg">添加图片</label></button>

                <input style="display: none;" class="button" type="file" accept="image/*" name="uploader-input" class="uploader-file" id="addimg">

            </div>

            <button class="button" id="addtext">添加文字</button>

            <button class="button" id="remove">删除元素</button>

            <button class="button" id="exportCanves">导出图片</button>

        </div>

const exportCanves = document.getElementById("exportCanves")

        const addimg = document.getElementById("addimg")

        const addBgImg = document.getElementById("addBgImg")

        const addtext = document.getElementById("addtext")

        const remove = document.getElementById("remove")

function downloadIamge(imgsrc, name) { //下载图片地址和图片名

            let image = new Image();

            // 解决跨域 Canvas 污染问题

            image.setAttribute("crossOrigin", "anonymous");

            image.onload = function() {

                let canvas = document.createElement("canvas");

                canvas.width = image.width;

                canvas.height = image.height;

                let context = canvas.getContext("2d");

                context.drawImage(image, 0, 0, image.width, image.height);

                let url = canvas.toDataURL("image/png"); //得到图片的base64编码数据

                let a = document.createElement("a"); // 生成一个a元素

                let event = new MouseEvent("click"); // 创建一个单击事件

                a.download = name || "photo"; // 设置图片名称

                a.href = url; // 将生成的URL设置为a.href属性

                a.dispatchEvent(event); // 触发a的单击事件

            };

            image.src = imgsrc;

        }

        //点击添加背景图片

        addBgImg.onchange = function(event) {

            var $file = event.currentTarget;

            var formData = new FormData();

            var file = $file.files;

            formData = new FormData();

            formData.append(file[0].name, file[0]);

            var path = window.URL.createObjectURL(file[0]);

            // 读取图片地址,设置画布背景图片

            fabric.Image.fromURL(path, (img) => {

                img.set({

                    // 通过scale来设置图片大小,这里设置和画布一样大

                    scaleX: card.width / img.width,

                    scaleY: card.height / img.height,

                });

                // 设置背景

                card.setBackgroundImage(img, card.renderAll.bind(card));

                card.renderAll();

            });

        }

        // 点击添加文字

        addtext.onclick = function(e) {

            getDialog()

        }

        // 点击删除元素

        remove.onclick = function(e) {

            card.remove(card.getActiveObject())

            card.renderAll()

        }

        // 当对象移动时 限制对象的 不超出画布

        function objectMoving(e) {

            var obj = e.target;

            if (obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width) {

                return;

            }

            obj.setCoords();

            // top-left corner

            if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {

                obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);

                obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left);

            }

            // bot-right corner

            if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width) {

                obj.top = Math.min(obj.top, obj.canvas.height -

                    obj.getBoundingRect().height + obj.top -

                    obj.getBoundingRect().top);

                obj.left = Math.min(obj.left, obj.canvas.width -

                    obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left);

            }

        }

        // 当画布对象放大时限制其操出边界:

        //在对象修改时方法里就可以调用了。

        function objectModified(e) {

            let obj = e.target;

            let boundingRect = obj.getBoundingRect(true);

            if (boundingRect.left < 0 || boundingRect.top < 0 || boundingRect.left + boundingRect.width > obj.canvas.getWidth() || boundingRect.top + boundingRect.height > obj.canvas.getHeight()) {

                obj.top = obj._stateProperties.top;

                obj.left = obj._stateProperties.left;

                obj.angle = obj._stateProperties.angle;

                obj.scaleX = obj._stateProperties.scaleX;

                obj.scaleY = obj._stateProperties.scaleY;

                obj.setCoords();

                obj.saveState();

            } else {

                obj.saveState();

            }

        }

        //输入弹框

        function getDialog() {

            window.CocoConfig = {

                maskClose: true,

                header: true,

                footer: true,

                title: '提示',

                text: '',

                width: '300px',

                top: '30vh',

                inputAttrs: true,

                escClose: true,

                okButton: true,

                cancelButton: true,

                okText: '确定',

                cancelText: '取消',

                closeButton: true,

                html: '',

                zIndexOfModal: 1995,

                zIndexOfMask: 2008,

                zIndexOfActiveModal: 2020,

                autoFocusOkButton: true,

                autoFocusInput: true,

                fullScreen: false,

                borderRadius: '6px',

                blur: false,

                buttonColor: '#4285ff',

                hooks: {

                    open() {},

                    opened() {

                    },

                    close() {},

                    closed() {},

                },

                destroy: true,

                animation: true

            }

            coco({

                title: "请输入你要添加的内容",

                html: '',

            }).onClose((cc, isOk, done) => {

                let tmr = null;

                if (cc) { //true 确认

                    isOk.showLoading();

                    if (addtextByCanves(isOk.inputValue)) {

                        isOk.hideLoading();

                        done();

                    }

                } else {

                    clearTimeout(tmr);

                    isOk.hideLoading();

                    done();

                }

            });

        }

        //将输入的内容加入到canves中

        function addtextByCanves(text) {

            const contant = new fabric.Textbox(text, {

                left: 50,

                top: 50,

                width: 150,

                fontSize: 20, // 字体大小

                fontWeight: 800, // 字体粗细

                // fill: 'red', // 字体颜色

                // fontStyle: 'italic',  // 斜体

                // fontFamily: 'Delicious', // 设置字体

                // stroke: 'green', // 描边颜色

                // strokeWidth: 3, // 描边宽度

                hasControls: false,

                borderColor: 'orange',

                editingBorderColor: 'blue' // 点击文字进入编辑状态时的边框颜色

            });

            // 添加文字

            card.add(contant);

            return true

        }

        //上传图片事件

        document.getElementById('addimg').addEventListener('change', function(event) {

            var $file = event.currentTarget;

            var formData = new FormData();

            var file = $file.files;

            formData = new FormData();

            formData.append(file[0].name, file[0]);

            let path = window.URL.createObjectURL(file[0]);

            addimgByCanves(path)

        });

        //将图片加入canves

        function addimgByCanves(imgUrl) {

            fabric.Image.fromURL(imgUrl, (img) => {

                img.set({

                    hasControls: true, // 是否开启图层的控件

                    borderColor: 'orange', // 图层控件边框的颜色

                });

                //生成的图片缩放到指定的尺寸。

                // img.scaleToWidth(500);

                img.scaleToHeight(100);

                // 添加对象

                card.add(img);

            });

        }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容