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

news2024/10/6 6:52:35

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

atlas 500容器(ubuntu20.04)搭建

1.docker 及环境搭建略 2.宿主机驱动安装略 3.宿主机中能正确使用npu-smi 4.docker 拉取略 5.docker 容器启动 docker run -itd --device/dev/davinci0 --device/dev/davinci_manager --device/dev/devmm_svm --device/dev/hisi_hdc -v /run/board_cfg.ini:/run/b…

springboot如何使用RedisTemplate

第一步&#xff1a;创建一个spring boot项目 第二步&#xff1a;pom导入redis相关依赖 <!--reids依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </depen…

swagger xss漏洞复现

swagger xss漏洞复现 文章目录 swagger xss漏洞复现漏洞介绍影响版本实现原理漏洞复现修复建议: 漏洞介绍 Swagger UI 有一个有趣的功能&#xff0c;允许您提供 API 规范的 URL - 一个 yaml 或 json 文件&#xff0c;将被获取并显示给用户 根本原因非常简单 - 一个过时的库Dom…

「51媒体」城市推介会,地方旅游推荐,怎么做好媒体宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 城市推介会和地方旅游推荐是城市形象宣传的重要组成部分&#xff0c;通过有效的媒体宣传可以提升城市的知名度和吸引力。&#xff1a; 一&#xff0c;活动内容层面&#xff1a; 突出亮点…

修改后门ctime | Linux 后门系列

0x00 前情提要 在 alias 后门 &#xff5c; Linux 后门系列一文中&#xff0c;我们为了让后门完美一些&#xff0c;修改了后门文件的 atime、mtime&#xff0c;但是 ctime 一直没有办法修改&#xff0c;今天我们来把这一块补齐&#xff0c;让后门更加完美 atime -> access t…

数据结构:最小生成树(Prim算法和Kruskal算法)、图的最短路径(Dijkstra算法和Bellman-Ford算法)

什么是最小生成树&#xff1f;Prim算法和Kruskal算法是如何找到最小生成树的&#xff1f; 最小生成树是指在一个连通图中&#xff0c;通过连接所有节点并使得总权重最小的子图。 Prim算法和Kruskal算法是两种常用的算法&#xff0c;用于寻找最小生成树。 Prim算法的步骤如下&…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-6.3

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

User Agent 解析:它是什么以及工作原理

什么是User Agent? UserAgent&#xff0c;简称UA&#xff0c;是一个使服务器能够识别用户使用的浏览器类型、版本以及运行浏览器的操作系统等信息的字符串。它作为浏览器请求头部信息的一部分发送给服务器&#xff0c;以便服务器可以返回合适格式和版本的内容。 跟Cookie一样…

x264 编码器源码分析综述

================================================================================ 系列文章 x264配置文章链接🔗Windows11编译x264源码https://blog.csdn.net/yanceyxin/article/details/135035650Mac调试x264源码https://blog.csdn.net/yanceyxin/article/details

想要应聘前端工程师——了解前端招聘需求

市场对前端工程师的需求依然旺盛。所谓知己知彼,百战不殆,分析各个公司对前端工程师的招聘需求,一方面可以了解到前端各细分领域在企业的需求情况,调整自己对岗位和薪资的期待,另一方面可以获得各种前端技术在企业中的应用情况,调整自己的学习和面试准备方向。因篇幅所限…

外网如何进行端口映射?

外网端口映射是一种网络技术&#xff0c;通过将外部网络请求映射到内部网络的指定端口&#xff0c;实现对内部网络资源的远程访问。它在各种应用场景中发挥着重要作用&#xff0c;为企业和个人提供了便捷的远程连接方式。本文将介绍外网端口映射的原理和应用&#xff0c;并重点…

《辐射4》次世代版本上线

B社刚刚发布了《辐射4》次世代版本&#xff0c;带来了许多令人期待的更新内容。这次更新的亮点包括新创作俱乐部内容如飞地遗迹和盔甲武器捆绑包&#xff0c;包括X-02动力装甲、地狱火动力装甲、重型焚烧炉、特斯拉大炮等。同时&#xff0c;新增了万圣节工作坊内容&#xff0c;…

阿里巴巴瓴羊基于 Flink 实时计算的优化和实践

摘要&#xff1a;本⽂整理⾃阿里云智能集团技术专家王柳焮⽼师在 Flink Forward Asia 2023 中平台建设专场的分享。内容主要为以下四部分&#xff1a; 阿里巴巴瓴羊基于 Flink 实时计算的平台演进Flink 能力优化与建设基于 Flink 的最佳实践未来规划 1. 阿里巴巴瓴羊基于 Flink…

python使用opencv对图像的基本操作(2)

13.对多个像素点进行操作&#xff0c;使用数组切片方式访问 img[i,:] img[j,:] #将第j行的数值赋值给第i行 img[-2,:]或img[-2] #倒数第二行 img[:,-1] #最后一列 img[50:100,50:100] #50-100行&#xff0c;50-100列&#xff08;不包括第100行和第100列&#xff09; img[:100…

陪丨玩丨系丨统前后端开发流程,APP小程序H5前后端源码交付支持二开!多人语音,开黑,线上线下两套操作可在一个系统完成!

100%全部源码出售 官网源码APP源码 管理系统源码 终身免费售后 产品免费更新 产品更新频率高 让您时刻立足于行业前沿 软件开发流程步骤及其作用&#xff1a; 软件开发是一个复杂而系统的过程&#xff0c;涉及多个环节&#xff0c;以下是软件开发的主要流程步骤及其作用…

合合信息引领AI场景化革新,供应链金融智能化审核全面升级!

官.网地址&#xff1a;合合TextIn - 合合信息旗下OCR云服务产品 随着供给侧结构性改革的深入推进和产业结构的不断升级&#xff0c;金融机构在监管部门的指导下&#xff0c;积极拓展供应链金融业务&#xff0c;取得了显著成效。这一举措有效缓解了上下游中小企业的融资困难&a…

01 校园人脸识别项目

前言 主要是来自于 朋友的需求 项目概况 项目情况如下 包含一个后端服务, 一个前端服务, 一个微信小程序服务 主要的业务流程如下 整体模型设计如下 一个班级下面 N 个学生, 一个班级根据实际情况老师进行课程安排 上课的时候 学生去对应的课程实验室进行上课, 上课…

HPE Aruba Networking推出新一代Wi-Fi 7接入点 助力企业高效应对安全、AI与物联网挑战

HPE ArubaNetworking推出的全新Wi-Fi 7接入点&#xff0c;提供全面的AI就绪边缘IT解决方案&#xff0c;旨在为用户和物联网设备提供安全、高性能的连接服务&#xff0c;以实现数据的捕获和路由&#xff0c;从而满足AI训练和推理需求 休斯顿-2024年4月23日-慧与科技(NYSE: HPE)近…

2024 java easyexcel poi word模板填充数据,多个word合成一个word

先看效果 一、准备工作 1.word模版 2.文件路径 二、pom依赖 <!-- easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.7</version></dependency><depe…

STM32、GD32等驱动AMG8833热成像传感器源码分享

一、AMG8833介绍 1简介 AMG8833是一种红外热像传感器&#xff0c;也被称为热感传感器。它可以用来检测和测量物体的热辐射&#xff0c;并将其转换为数字图像。AMG8833传感器可以感知的热源范围为-20C到100C&#xff0c;并能提供8x8的像素分辨率。它通过I2C接口与微控制器或单…