vue项目中基于fabric 插件实现涂鸦画布功能

vue项目中基于fabric 插件实现涂鸦画布功能

  • 一、效果图
  • 二、安装依赖
  • 三、main.js引入
  • 四、主要代码

一、效果图

在这里插入图片描述

二、安装依赖

 npm install fabric 

三、main.js引入

import fabric from 'fabric'
Vue.use(fabric);

四、主要代码

//封装成了一个组件
<template>
    <el-dialog
        title="涂鸦生图"
        :visible="visible"
        custom-class="doodleDialog"
        @close="handleClose"
        @open="openDialog"
        width="1500px"
    >
        <div style="display: flex; justify-content: space-between">
            <div class="rigth">
                <p style="font-size: 16px">涂鸦区</p>
                <div class="maintenancePlanAdd">
                    <div class="child-panel-title"></div>
                    <div class="panel-body">
                        <div class="demo">
                            <canvas id="canvas" :width="width" :height="height"></canvas>
                            <div class="draw-btn-group">
                                <div
                                    :class="{ active: drawType == '' }"
                                    title="自由选择"
                                    @click="drawTypeChange('')"
                                >
                                    <i class="draw-icon icon-mouse"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'arrow' }"
                                    title="画箭头"
                                    @click="drawTypeChange('arrow')"
                                >
                                    <i class="draw-icon icon-1"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'text' }"
                                    title="文本输入框"
                                    @click="drawTypeChange('text')"
                                >
                                    <i class="draw-icon icon-2"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'ellipse' }"
                                    title="画圆"
                                    @click="drawTypeChange('ellipse')"
                                >
                                    <i class="draw-icon icon-3"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'rectangle' }"
                                    title="画矩形"
                                    @click="drawTypeChange('rectangle')"
                                >
                                    <i class="draw-icon icon-4"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'polygon' }"
                                    title="画多边形"
                                    @click="drawPolygon"
                                >
                                    <i class="draw-icon icon-6"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'pen' }"
                                    title="笔画"
                                    @click="drawTypeChange('pen')"
                                >
                                    <i class="draw-icon icon-7"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'pentagram' }"
                                    title="五角星"
                                    @click="drawTypeChange('pentagram')"
                                >
                                    <i class="draw-icon icon-pentagram"></i>
                                </div>
                                <div
                                    :class="{ active: drawType == 'delete' }"
                                    title="删除"
                                    @click="drawTypeDelete()"
                                >
                                    <i style="font-size: 26px" class="el-icon-delete"></i>
                                </div>
                                <!-- <div @click="uploadImg" title="从文件选择图片上传">
              <i class="draw-icon icon-img"></i>
            </div>
            <div @click="loadExpImg" title="加载背景图">
              <i class="draw-icon icon-back"></i>
            </div>
            <div @click="save" title="保存">
              <i class="draw-icon icon-save"></i>
            </div> -->
                            </div>
                        </div>
                    </div>
                    <input type="file" @change="uploadImgChange" id="imgInput" accept="image/*" />
                    <img id="img" :src="imgSrc" />
                    <img id="expImg" src="../../../assets/images/draw/exp.jpg" />
                </div>
            </div>
            <div class="left" style="width: 600px">
                <p style="font-size: 16px">生成区</p>
                <div style="border: 1px dashed black">
                    <p style="text-align: center; margin-top: 5px">生成图片如下:</p>
                    <div style="width: 598px; height: 400px; margin-top: 18px; margin-bottom: 58px">
                        <img
                            v-if="resultImg"
                            style="width: 598px; height: 400px; display: inline-block"
                            :src="resultImg"
                            alt=""
                        />
                    </div>
                    <el-form
                        class="screenwaper"
                        :model="addInnerFrom"
                        :rules="addRules"
                        ref="addInnerFrom"
                        label-width="90px"
                        label-position="rigth"
                    >
                        <el-form-item label="文字描述:" prop="prompt">
                            <el-input
                                style="width: 500px"
                                v-model="addInnerFrom.prompt"
                                placeholder="请输入"
                                size="small"
                                type="textarea"
                                :rows="2"
                            ></el-input>
                        </el-form-item>
                        <el-form-item label="相似度:" prop="similarity">
                            <el-slider
                                style="width: 500px"
                                v-model="addInnerFrom.similarity"
                                :format-tooltip="formatTooltip"
                            ></el-slider>
                        </el-form-item>
                        <p style="text-align: center; margin: 20px 0 30px">
                            <el-button
                                size="small"
                                type="primary"
                                :loading="loading"
                                @click="handleSureDialog('addInnerFrom')"
                            >
                                确定生成
                            </el-button>
                        </p>
                    </el-form>
                </div>
            </div>
        </div>

        <div style="text-align: center; padding: 20px 0 0">
            <el-button size="small" @click="handleSure">关 闭</el-button>
        </div>
    </el-dialog>
</template>

<script>
import { fabric } from 'fabric';
import { doodleImg } from '../api';
export default {
    name: 'doodleDialog',
    props: {
        visible: {
            type: Boolean,
            default: false,
        },
    },
    data() {
        return {
            addInnerFrom: { prompt: '', imageUploadData: '', similarity: 0 },
            addRules: {
                prompt: [{ required: true, message: '请输入标题', trigger: 'blur' }],
                similarity: [{ required: true, message: '请输入文本描述', trigger: 'blur' }],
            },
            loading: false,

            width: 800,
            height: 700,
            rect: [],
            canvas: {},
            showMenu: false,
            x: '',
            y: '',

            mouseFrom: {},
            mouseTo: {},
            drawType: null, //当前绘制图像的种类
            canvasObjectIndex: 0,
            textbox: null,
            rectangleLabel: 'warning',
            drawWidth: 2, //笔触宽度
            color: '#E34F51', //画笔颜色
            drawingObject: null, //当前绘制对象
            moveCount: 1, //绘制移动计数器
            doDrawing: false, // 绘制状态

            //polygon 相关参数
            polygonMode: false,
            pointArray: [],
            lineArray: [],
            activeShape: false,
            activeLine: '',
            line: {},

            delectKlass: {},
            imgFile: {},
            imgSrc: '',
            resultImg: '',
        };
    },
    watch: {
        drawType() {
            this.canvas.selection = !this.drawType;
        },
        width() {
            this.canvas.setWidth(this.width);
        },
        height() {
            this.canvas.setHeight(this.height);
        },
    },
    methods: {
        formatTooltip(val) {
            return val / 100;
        },
        openDialog() {
            this.resultImg = '';
            this.loading = false;
            this.addInnerFrom = { prompt: '', imageUploadData: '', similarity: 0 };
            this.$nextTick(() => {
                this.canvas = new fabric.Canvas('canvas', {
                    // skipTargetFind: false, //当为真时,跳过目标检测。目标检测将返回始终未定义。点击选择将无效
                    // selectable: false,  //为false时,不能选择对象进行修改
                    // selection: false   // 是否可以多个对象为一组
                });
                this.canvas.selectionColor = 'rgba(0,0,0,0.05)';
                this.canvas.on('mouse:down', this.mousedown);
                this.canvas.on('mouse:move', this.mousemove);
                this.canvas.on('mouse:up', this.mouseup);

                document.onkeydown = e => {
                    // 键盘 delect删除所选元素
                    if (e.keyCode == 46) {
                        this.deleteObj();
                    }
                    // ctrl+z 删除最近添加的元素
                    if (e.keyCode == 90 && e.ctrlKey) {
                        this.canvas.remove(
                            this.canvas.getObjects()[this.canvas.getObjects().length - 1]
                        );
                    }
                };
            });
        },
        //  画布下面删除按钮
        drawTypeDelete() {
            this.drawType = 'delete';
            this.canvas.clear();
        },
        handleClose() {
            this.canvas.clear();
            this.$emit('DialogCancel');
        },
        handleSure() {
            this.handleClose();
            this.$emit('DialogOk', this.resultImg);
        },
        handleSureDialog(From) {
            this.$refs[From].validate(valid => {
                if (valid) {
                    this.loading = true;
                    let canvas = document.getElementById('canvas');
                    this.addInnerFrom.imageUploadData = canvas.toDataURL('png');
                    doodleImg({
                        imageFile: this.addInnerFrom.imageUploadData,
                        prompt: this.addInnerFrom.prompt,
                        similarity: this.formatTooltip(this.addInnerFrom.similarity),
                    }).then(({ data }) => {
                        if (data.length) {
                            this.loading = false;
                            this.resultImg = data[0];
                            this.$message({
                                showClose: true,
                                message: `已成功生成图片${data.length}`,
                                type: 'success',
                            });
                        }
                    });
                } else {
                    console.log('error submit!!');
                    return false;
                }
            });
        },
        // 保存当前画布为png图片
        save() {
            let canvas = document.getElementById('canvas');
            let imgData = canvas.toDataURL('png');
            console.log(imgData, 'wwww');
            imgData = imgData.replace('image/png', 'image/octet-stream');

            // 下载后的问题名,可自由指定
            let filename = 'drawingboard_' + new Date().getTime() + '.' + 'png';
            this.saveFile(imgData, filename);
        },
        saveFile(data, filename) {
            let save_link = document.createElement('a');
            save_link.href = data;
            save_link.download = filename;

            let event = document.createEvent('MouseEvents');
            event.initMouseEvent(
                'click',
                true,
                false,
                window,
                0,
                0,
                0,
                0,
                0,
                false,
                false,
                false,
                false,
                0,
                null
            );
            save_link.dispatchEvent(event);
        },
        uploadImg() {
            document.getElementById('imgInput').click();
        },
        // 从已渲染的DOM元素加载图片至canvas
        loadExpImg() {
            let imgElement = document.getElementById('expImg'); //声明我们的图片
            let imgInstance = new fabric.Image(imgElement, {
                selectable: false,
                // zIndex:-99,
            });
            this.canvas.add(imgInstance);
        },
        // 从文件加载图片至canvas
        uploadImgChange() {
            // 获取文件
            let eleImportInput = document.getElementById('imgInput');
            this.imgFile = eleImportInput.files[0];
            let imgSrc = '',
                imgTitle = '';
            // 从reader中获取选择文件的src
            if (/\.(jpe?g|png|gif)$/i.test(this.imgFile.name)) {
                let reader = new FileReader();
                let _this = this;
                reader.addEventListener(
                    'load',
                    function () {
                        imgTitle = _this.imgFile.name;
                        _this.imgSrc = this.result;
                    },
                    false
                );
                reader.readAsDataURL(this.imgFile);
            }
            let imgElement = document.getElementById('img'); //声明我们的图片

            imgElement.onload = () => {
                this.width = imgElement.width;
                this.height = imgElement.height;
                let imgInstance = new fabric.Image(imgElement, {
                    zIndex: -1,
                    selectable: false,
                });
                this.canvas.add(imgInstance);
            };
        },
        // 开始绘制时,指定绘画种类
        drawTypeChange(e) {
            this.drawType = e;
            this.canvas.skipTargetFind = !!e;
            if (e == 'pen') {
                // isDrawingMode为true 才可以自由绘画
                this.canvas.isDrawingMode = true;
            } else {
                this.canvas.isDrawingMode = false;
            }
        },
        // 鼠标按下时触发
        mousedown(e) {
            // 记录鼠标按下时的坐标
            let xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
            this.mouseFrom.x = xy.x;
            this.mouseFrom.y = xy.y;
            this.doDrawing = true;
            if (this.drawType == 'text') {
                this.drawing();
            }

            if (this.textbox) {
                this.textbox.enterEditing();
                this.textbox.hiddenTextarea.focus();
            }
            // 绘制多边形
            if (this.drawType == 'polygon') {
                this.canvas.skipTargetFind = false;
                try {
                    // 此段为判断是否闭合多边形,点击红点时闭合多边形
                    if (this.pointArray.length > 1) {
                        // e.target.id == this.pointArray[0].id 表示点击了初始红点
                        if (e.target && e.target.id == this.pointArray[0].id) {
                            this.generatePolygon();
                        }
                    }
                    //未点击红点则继续作画
                    if (this.polygonMode) {
                        this.addPoint(e);
                    }
                } catch (error) {
                    console.log(error);
                }
            }
        },
        // 鼠标松开执行
        mouseup(e) {
            let xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
            this.mouseTo.x = xy.x;
            this.mouseTo.y = xy.y;
            this.drawingObject = null;
            this.moveCount = 1;
            if (this.drawType != 'polygon') {
                this.doDrawing = false;
            }
        },

        //鼠标移动过程中已经完成了绘制
        mousemove(e) {
            if (this.moveCount % 2 && !this.doDrawing) {
                //减少绘制频率
                return;
            }
            this.moveCount++;
            let xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
            this.mouseTo.x = xy.x;
            this.mouseTo.y = xy.y;
            // 多边形与文字框特殊处理
            if (this.drawType != 'text' || this.drawType != 'polygon') {
                this.drawing(e);
            }
            if (this.drawType == 'polygon') {
                if (this.activeLine && this.activeLine.class == 'line') {
                    let pointer = this.canvas.getPointer(e.e);
                    this.activeLine.set({ x2: pointer.x, y2: pointer.y });

                    let points = this.activeShape.get('points');
                    points[this.pointArray.length] = {
                        x: pointer.x,
                        y: pointer.y,
                        zIndex: 1,
                    };
                    this.activeShape.set({
                        points: points,
                    });
                    this.canvas.renderAll();
                }
                this.canvas.renderAll();
            }
        },
        deleteObj() {
            this.canvas.getActiveObjects().map(item => {
                this.canvas.remove(item);
            });
        },
        transformMouse(mouseX, mouseY) {
            return { x: mouseX / 1, y: mouseY / 1 };
        },
        // 绘制多边形开始,绘制多边形和其他图形不一样,需要单独处理
        drawPolygon() {
            this.drawType = 'polygon';
            this.polygonMode = true;
            //这里画的多边形,由顶点与线组成
            this.pointArray = new Array(); // 顶点集合
            this.lineArray = new Array(); //线集合
            this.canvas.isDrawingMode = false;
        },
        addPoint(e) {
            let random = Math.floor(Math.random() * 10000);
            let id = new Date().getTime() + random;
            let circle = new fabric.Circle({
                radius: 5,
                fill: '#ffffff',
                stroke: '#333333',
                strokeWidth: 0.5,
                left: (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
                top: (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
                selectable: false,
                hasBorders: false,
                hasControls: false,
                originX: 'center',
                originY: 'center',
                id: id,
                objectCaching: false,
            });
            if (this.pointArray.length == 0) {
                circle.set({
                    fill: 'red',
                });
            }
            let points = [
                (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
                (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
                (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
                (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
            ];

            this.line = new fabric.Line(points, {
                strokeWidth: 2,
                fill: '#999999',
                stroke: '#999999',
                class: 'line',
                originX: 'center',
                originY: 'center',
                selectable: false,
                hasBorders: false,
                hasControls: false,
                evented: false,

                objectCaching: false,
            });
            if (this.activeShape) {
                let pos = this.canvas.getPointer(e.e);
                let points = this.activeShape.get('points');
                points.push({
                    x: pos.x,
                    y: pos.y,
                });
                let polygon = new fabric.Polygon(points, {
                    stroke: '#333333',
                    strokeWidth: 1,
                    fill: '#cccccc',
                    opacity: 0.3,

                    selectable: false,
                    hasBorders: false,
                    hasControls: false,
                    evented: false,
                    objectCaching: false,
                });
                this.canvas.remove(this.activeShape);
                this.canvas.add(polygon);
                this.activeShape = polygon;
                this.canvas.renderAll();
            } else {
                let polyPoint = [
                    {
                        x: (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
                        y: (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
                    },
                ];
                let polygon = new fabric.Polygon(polyPoint, {
                    stroke: '#333333',
                    strokeWidth: 1,
                    fill: '#cccccc',
                    opacity: 0.3,

                    selectable: false,
                    hasBorders: false,
                    hasControls: false,
                    evented: false,
                    objectCaching: false,
                });
                this.activeShape = polygon;
                this.canvas.add(polygon);
            }
            this.activeLine = this.line;

            this.pointArray.push(circle);
            this.lineArray.push(this.line);
            this.canvas.add(this.line);
            this.canvas.add(circle);
        },
        generatePolygon() {
            let points = new Array();
            this.pointArray.map((point, index) => {
                points.push({
                    x: point.left,
                    y: point.top,
                });
                this.canvas.remove(point);
            });
            this.lineArray.map((line, index) => {
                this.canvas.remove(line);
            });
            this.canvas.remove(this.activeShape).remove(this.activeLine);
            let polygon = new fabric.Polygon(points, {
                stroke: this.color,
                strokeWidth: this.drawWidth,
                fill: 'rgba(255, 255, 255, 0)',
                opacity: 1,
                hasBorders: true,
                hasControls: false,
            });
            this.canvas.add(polygon);

            this.activeLine = null;
            this.activeShape = null;
            this.polygonMode = false;
            this.doDrawing = false;
            this.drawType = null;
        },
        drawing(e) {
            if (this.drawingObject) {
                this.canvas.remove(this.drawingObject);
            }
            let canvasObject = null;
            let left = this.mouseFrom.x,
                top = this.mouseFrom.y,
                mouseFrom = this.mouseFrom,
                mouseTo = this.mouseTo;
            switch (this.drawType) {
                case 'arrow':
                    {
                        //箭头
                        let x1 = mouseFrom.x,
                            x2 = mouseTo.x,
                            y1 = mouseFrom.y,
                            y2 = mouseTo.y;
                        let w = x2 - x1,
                            h = y2 - y1,
                            sh = Math.cos(Math.PI / 4) * 16;
                        let sin = h / Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2));
                        let cos = w / Math.sqrt(Math.pow(w, 2) + Math.pow(h, 2));
                        let w1 = (16 * sin) / 4,
                            h1 = (16 * cos) / 4,
                            centerx = sh * cos,
                            centery = sh * sin;
                        /**
                         * centerx,centery 表示起始点,终点连线与箭头尖端等边三角形交点相对x,y
                         * w1 ,h1用于确定四个点
                         */

                        let path = ' M ' + x1 + ' ' + y1;
                        path += ' L ' + (x2 - centerx + w1) + ' ' + (y2 - centery - h1);
                        path += ' L ' + (x2 - centerx + w1 * 2) + ' ' + (y2 - centery - h1 * 2);
                        path += ' L ' + x2 + ' ' + y2;
                        path += ' L ' + (x2 - centerx - w1 * 2) + ' ' + (y2 - centery + h1 * 2);
                        path += ' L ' + (x2 - centerx - w1) + ' ' + (y2 - centery + h1);
                        path += ' Z';
                        canvasObject = new fabric.Path(path, {
                            stroke: this.color,
                            fill: this.color,
                            strokeWidth: this.drawWidth,
                        });
                    }
                    break;
                case 'pentagram':
                    {
                        //五角星
                        let x1 = mouseFrom.x,
                            x2 = mouseTo.x,
                            y1 = mouseFrom.y,
                            y2 = mouseTo.y;
                        /**
                         * 实现思路  (x1,y1)表示鼠标起始的位置 (x2,y2)表示鼠标抬起的位置
                         * r 表示五边形外圈圆的半径,这里建议自己画个图理解
                         * 正五边形夹角为36度。计算出cos18°,sin18°备用
                         */
                        let w = Math.abs(x2 - x1),
                            h = Math.abs(y2 - y1),
                            r = Math.sqrt(w * w + h * h);
                        let cos18 = Math.cos((18 * Math.PI) / 180);
                        let sin18 = Math.sin((18 * Math.PI) / 180);

                        /**
                         * 算出对应五个点的坐标转化为路径
                         */
                        let point1 = [x1, y1 + r];
                        let point2 = [x1 + 2 * r * sin18, y1 + r - 2 * r * cos18];
                        let point3 = [x1 - r * cos18, y1 + r * sin18];
                        let point4 = [x1 + r * cos18, y1 + r * sin18];
                        let point5 = [x1 - 2 * r * sin18, y1 + r - 2 * r * cos18];

                        let path = ' M ' + point1[0] + ' ' + point1[1];
                        path += ' L ' + point2[0] + ' ' + point2[1];
                        path += ' L ' + point3[0] + ' ' + point3[1];
                        path += ' L ' + point4[0] + ' ' + point4[1];
                        path += ' L ' + point5[0] + ' ' + point5[1];
                        path += ' Z';
                        canvasObject = new fabric.Path(path, {
                            stroke: this.color,
                            fill: this.color,
                            strokeWidth: this.drawWidth,
                            // angle:180,  //设置旋转角度
                        });
                    }
                    break;
                case 'ellipse':
                    {
                        //椭圆
                        // 按shift时画正圆,只有在鼠标移动时才执行这个,所以按了shift但是没有拖动鼠标将不会画圆
                        if (e.e.shiftKey) {
                            mouseTo.x - left > mouseTo.y - top
                                ? (mouseTo.y = top + mouseTo.x - left)
                                : (mouseTo.x = left + mouseTo.y - top);
                        }
                        let radius =
                            Math.sqrt(
                                (mouseTo.x - left) * (mouseTo.x - left) +
                                    (mouseTo.y - top) * (mouseTo.y - top)
                            ) / 2;
                        canvasObject = new fabric.Ellipse({
                            left: (mouseTo.x - left) / 2 + left,
                            top: (mouseTo.y - top) / 2 + top,
                            stroke: this.color,
                            fill: 'rgba(255, 255, 255, 0)',
                            originX: 'center',
                            originY: 'center',
                            rx: Math.abs(left - mouseTo.x) / 2,
                            ry: Math.abs(top - mouseTo.y) / 2,
                            strokeWidth: this.drawWidth,
                        });
                    }
                    break;
                case 'rectangle':
                    {
                        //长方形
                        // 按shift时画正方型
                        if (e.e.shiftKey) {
                            mouseTo.x - left > mouseTo.y - top
                                ? (mouseTo.y = top + mouseTo.x - left)
                                : (mouseTo.x = left + mouseTo.y - top);
                        }
                        let path =
                            'M ' +
                            mouseFrom.x +
                            ' ' +
                            mouseFrom.y +
                            ' L ' +
                            mouseTo.x +
                            ' ' +
                            mouseFrom.y +
                            ' L ' +
                            mouseTo.x +
                            ' ' +
                            mouseTo.y +
                            ' L ' +
                            mouseFrom.x +
                            ' ' +
                            mouseTo.y +
                            ' L ' +
                            mouseFrom.x +
                            ' ' +
                            mouseFrom.y +
                            ' z';
                        canvasObject = new fabric.Path(path, {
                            left: left,
                            top: top,
                            stroke: this.color,
                            strokeWidth: this.drawWidth,
                            fill: 'rgba(255, 255, 255, 0)',
                            hasControls: false,
                        });
                    }
                    //也可以使用fabric.Rect
                    break;
                case 'text':
                    {
                        //文本框
                        this.textbox = new fabric.Textbox('', {
                            left: mouseFrom.x,
                            top: mouseFrom.y - 10,
                            // width: 150,
                            fontSize: 16,
                            borderColor: this.color,
                            fill: this.color,
                            hasControls: false,
                        });
                        this.canvas.add(this.textbox);
                        this.textbox.enterEditing();
                        this.textbox.hiddenTextarea.focus();
                    }
                    break;

                default:
                    break;
            }

            if (canvasObject) {
                // canvasObject.index = getCanvasObjectIndex();\
                this.canvas.add(canvasObject); //.setActiveObject(canvasObject)
                this.drawingObject = canvasObject;
            }
        },
    },
};
</script>

<style lang="scss" scope>
.doodleDialog {
    .el-container {
        flex-direction: column;
    }
    img,
    input {
        display: none;
    }
    .demo {
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    canvas {
        border: 1px dashed black;
    }
    .draw-btn-group {
        // width: 1270px;
        margin-top: 10px;
        display: flex;
        align-items: center;
        justify-content: flex-start;
        & > div {
            background: #fafafa;
            cursor: pointer;
            &:hover {
                background: #eee;
            }
            i {
                display: flex;
                background-repeat: no-repeat;
                background-size: 80%;
                background-position: 50% 50%;
                height: 30px;
                width: 30px;
            }
            .icon-1 {
                background-image: url('../../../assets/images/draw/1.png');
            }
            .icon-pentagram {
                background-image: url('../../../assets/images/draw/pentagram.png');
            }
            .icon-2 {
                background-image: url('../../../assets/images/draw/2.png');
            }
            .icon-3 {
                background-image: url('../../../assets/images/draw/3.png');
            }
            .icon-4 {
                background-image: url('../../../assets/images/draw/4.png');
                background-size: 75%;
            }
            .icon-5 {
                background-image: url('../../../assets/images/draw/5.png');
                background-size: 70%;
            }
            .icon-6 {
                background-image: url('../../../assets/images/draw/6.png');
            }
            .icon-7 {
                background-image: url('../../../assets/images/draw/7.png');
                background-size: 80%;
            }
            .icon-del {
                background-image: url('../../../assets/images/draw/del.png');
                background-size: 90%;
            }
            .icon-img {
                background-image: url('../../../assets/images/draw/img.png');
                background-size: 80%;
            }
            .icon-back {
                background-image: url('../../../assets/images/draw/back.png');
                background-size: 75%;
            }
            .icon-save {
                background-image: url('../../../assets/images/draw/save.png');
                background-size: 80%;
            }
            .icon-mouse {
                background-image: url('../../../assets/images/draw/mouse.png');
                background-size: 60%;
            }
        }
        .active {
            background: #eee;
        }
    }
}
</style>
<style lang="scss"></style>

链接: https://www.jianshu.com/p/d6d924eb5cf7
链接: https://github.com/Couy69/vue-fabric-drawingboard

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/564350.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图片/视频上传(超简单教程)

#应用场景# 该后端开发接口适用 图片/视频上传&#xff0c;返回路径名称场景 1.视频上传 写在Controller层 这里只是一个接收&#xff0c;调用uploadObject方法上传oss public OmsResult<FileUploadDto> goodsUploadVideo(RequestParam(value "file") Mu…

【力扣 Hot100 | 第七天】4.22(找到字符串中所有字母异位词)

文章目录 2.找到字符串中所有字母异位词2.1题目2.2解法&#xff1a;滑动窗口2.2.1解题思路2.2.2代码实现 2.找到字符串中所有字母异位词 2.1题目 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺…

28map,set关联式容器

序列式容器 vector、list、deque(单纯的纯数据) 关联式容器 map、set&#xff0c;哈希&#xff0c;&#xff0c;&#xff0c;(数据之间有很强的关联性,不是单纯的为了存储数据) Set 底层是搜索树&#xff08;红黑树&#xff09; T是key &#xff0c;compare是仿函数&#xff0…

150G全国1米分辨率土地利用数据【2023年】

#1数据摘要 全国1米分辨率土地利用数据 全国范围,分省份分类,1米精度土地利用数据。2023年版本。 数据格式:tif 坐标系:wgs1984 范围:全国各省份 时间:2023年 精度:1米 地类:共计11个地类 中国1m分辨率土地覆盖数据 文件命名与介绍:数据为GeoTIFF栅格格式,每个城市…

lvgl图形化设计工具GUI Guider结合使用

前言 上篇博客整合了lvgl到项目中&#xff0c;采用的是自己编写源码的方式&#xff0c;实现了个简单的界面。实际过程中一般情况开发界面都借助设计工具&#xff0c;这里使用的是gui guider来进行示例记录 项目结构&#xff08;生成代码路径依然放到项目路径下&#xff09; C…

知识图谱的起源与发展

文章目录 知识图谱的发展历史知识图谱的重要性知识图谱与Ontology、语义网络之间的区别知识图谱的定义 知识图谱的发展历史 知识图谱始于20世纪50年代&#xff0c;至今大致分为三个发展阶段&#xff1a;第一阶段 &#xff08;1955年—1977年&#xff09;是知识图谱的起源阶段&a…

ThingsBoard教程(二十九):详细讲解在tb平台下 http协议下的客户端rpc,服务的rpc的使用

客户端rpc 先来说一下简单的客户端rpc, 客户端发起rpc请求,只需要使用post方法调用该接口即可以 http://host:port/api/v1/$ACCESS_TOKEN/rpc请求路径中间的参数 ACCESS_TOKEN 必须是设备的访问令牌。 请求携带的参数如下,二个参数method和params {"method": …

uniapp H5的弹窗滚动穿透解决

目录 方案一 事件修饰符 overscroll-behavior 修饰符 overscroll-behavior 属性 看个案例 兼容 方案二 overflow&#xff1a;hiden 有一层遮罩蒙层覆盖在body上时&#xff0c;当我们滚动遮罩层&#xff0c;它下面的内容也会跟着一起滚动&#xff0c;看起来好像是上面的…

ChatGPT研究论文提示词集合4-【结论】、【论文写作】和【审稿与修订】

点击下方▼▼▼▼链接直达AIPaperPass &#xff01; AIPaperPass - AI论文写作指导平台 目录 1.结论 2.论文写作 3.评审和修订 4.书籍介绍 AIPaperPass智能论文写作平台 近期小编按照学术论文的流程&#xff0c;精心准备一套学术研究各个流程的提示词集合。总共14个步骤…

Numpy方法总结(二)

一. 高级索引 相比于基本索引&#xff0c;高级索引可以访问到数组中的任意元素&#xff0c;并且可以用来对数组进行复杂的操作和修改。 1.整数数组索引 整数数组索引是指使用一个数组来访问另一个数组的元素。这个数组中的每个元素都是目标数组中某个维度上的索引值。 示例…

Ghost Buster Pro for Mac:强大的系统优化工具

Ghost Buster Pro for Mac是一款功能强大的系统优化工具&#xff0c;专为Mac用户设计&#xff0c;旨在提供全方位的系统清理、优化和维护服务。 Ghost Buster Pro for Mac v3.2.5激活版下载 这款软件拥有出色的垃圾清理能力&#xff0c;能够深度扫描并清除Mac上的无效目录、文件…

《从零开始的Java世界》07常用类与基础API

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

尾矿库安全监测:仪器埋设与维护的关键要求

尾矿库作为矿业生产的重要组成部分&#xff0c;其安全运营对于保障人员生命安全和环境保护具有至关重要的意义。为了确保尾矿库的安全运行&#xff0c;及时发现潜在的安全隐患&#xff0c;必须采取有效的安全监测措施。本文将重点探讨尾矿库安全监测仪器的埋设及维护要求。 一、…

​「Python绘图」绘制皮卡丘

python 绘制皮卡丘 一、预期结果 二、关键代码 import turtle print("开始绘制皮卡丘") def getPosition(x, y):turtle.setx(x)turtle.sety(y)print(x, y)class Pikachu:def __init__(self):self.t turtle.Turtle()t self.tt.pensize(3)t.speed(190)t.ondrag(getP…

Linux命令接着学习

which命令&#xff0c;找到各种命令程序所处在的位置 语法&#xff1a;which查找的命令 那么对于我们想查找其他类型文件所在的位置&#xff0c;我们可以用到find命令 find命令 选项为-name&#xff0c;表示按照文件名进行查找 find命令中通配符 find命令和前面rm命令一样&…

Linux及tmux、vim常用命令

Linux 关于Linux的简介、诞生、迭代&#xff0c;大家可以去网上查一查&#xff0c;这里不多做赘述了 Linux文件类型 非常重要的文件类型有: 普通文件&#xff0c;目录文件&#xff0c;链接文件&#xff0c;设备文件&#xff0c;管道文件&#xff0c;Socket 套接字文件 等。 …

2024团体设计天梯赛之L1-101 别再来这么多猫娘了

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

D咖饮品机器人惊艳亮相:智能硬件改变生活习惯

在当今快节奏的生活中&#xff0c;人们对于便捷、高效的需求日益增长&#xff0c;智能硬件应运而生&#xff0c;其中饮品机器人作为一种新型的智能设备&#xff0c;正在以惊艳的姿态亮相于人们的生活中&#xff0c;为人们带来了全新的消费体验&#xff0c;改变着大众的生活习惯…

CV算法工程师的LLM日志(5)Mixture-of-depths——transformers改进结构 【15分钟代码和原理速通】

前言 简而言之&#xff0c;这是google对transformer一些改进设计&#xff0c;如果这个有效性能够证明并普及&#xff0c;那么下一个大模型的transformer范式就是这个了&#xff0c;当然同时也存在mamba和transformer的jamba崛起&#xff0c;不过现在主流还是transformer&#…