fabric.js基於canvas的畫板



<template> <div class="wraper" id="sketchpad-template" ref="wraper"> <div class="canvas-wraper"> <canvas id="canvas" ref="canvas"></canvas> </div> <div class="controlPanel"> <div class="text"> <p>視點塗鴉工具</p> </div> <div class="tool"> <div :class="[initIdx==idx ? 'contro-item active' : 'contro-item']" v-for="(item,idx) in toolsArr" :key="idx" @click="handleTools(item, idx)"> <i :class="'iconfont' + item.icon" :title="item.title"></i> </div> <colorPicker class="color-list" v-model="drawColor" v-on:change="headleChangeColor"/> <Slider v-model="drawWidth" :min=0 :max=50 style="width:150px;padding:0px 10px"></Slider> </div> </div> <div class="download"> <img :src="imageBase64" v-show="imageBase64!=''" alt=""> </div> </div> </template> <script> import {fabric} from 'fabric';
    import {Slider} from 'element-ui';
    import { setTimeout } from 'timers';
    export default {
        name:'Sketchpad',
        props: {
            imgSrc: {
                type: String
            },
            canvasHeight:{
                type:Number
            }
        },
        data() {
            return {
                onRemoveVaule:false,
                onStartvalue:false,
                currentTool: '',
                done: false,
                fabricObj: null,
                initIdx: 0,
                toolsArr: [{
                    name: 'pencil',
                    icon: ' iconhuabi',
                    title:'鉛筆'
                },
                {
                    name: 'line',
                    icon: ' iconline',
                    title:'直線'
                },
                {
                    name: 'arrow',
                    icon: ' iconhj2',
                    title:'箭頭'
                },
                {
                    name: 'xuxian',
                    icon: ' icon-xiaoxuxian',
                    title:'虛線'
                },
                {
                    name: 'text',
                    icon: ' iconwenzi1',
                    title:'文本'
                },
                {
                    name: 'juxing',
                    icon: ' iconjuxing',
                    title:'矩形'
                },
                {
                    name: 'cricle',
                    icon: ' iconyuanxingxuankuang',
                    title:'圓形'
                },
                {
                    name: 'ellipse',
                    icon: ' icontuoyuanxing',
                    title:'橢圓形'
                },
                {
                    name: 'equilateral', //三角形
                    icon: ' iconxingzhuang-sanjiaoxing',
                    title:'三角形'
                },
                {
                    name: 'remove',
                    icon: ' iconqingchu1',
                    title:'清除'
                },
                {
                    name: 'undo',
                    icon: ' iconzhixiang-houtui',
                    title:'上一步'
                },
                {
                    name: 'redo',
                    icon: ' iconzhixiang-qianjin',
                    title:'下一步'
                },
                {
                    name: 'reset',
                    icon: ' iconzhongzhi',
                    title:'重置'
                },
                {
                    name: 'test',
                    icon: ' iconicon-test',
                    title:'選中'
                },
                {
                    name: 'baocun',
                    icon: ' iconbaocun',
                    title:'保存'
                }],
                mouseFrom:{},
                mouseTo:{},
                moveCount: 1,
                doDrawing: false,
                fabricHistoryJson: [],
                mods: 0,
                drawingObject: null, //繪制對象
                // drawColor: '#E34F51',
                drawWidth: 4,
                drawColor:'#E34F51',
                imageBase64: '',
                zoom: window.zoom ? window.zoom : 1,
            }
            
        },
        components:{
            Slider
        },
        mounted() {
        //初始化canvas 
        setTimeout(()=>{
            this.initCanvas()
        },500)
        },
        watch:{
            'drawWidth': function(newVal){
                this.drawWidth = newVal;
                this.fabricObj.freeDrawingBrush.width = newVal
            },
        },
        computed:{
        canvasWidth() {
            return window.innerWidth
        }
        },
        methods:{
            initCanvas() {
                // let _this = this;
                this.fabricObj = new fabric.Canvas('canvas',{
                    isDrawingMode: true,  // 開啟畫筆
                    selectable: false,
                    selection: false,
                    //   perPixelTargetFind: true,
                    devicePixelRatio:true, //Retina 高清屏 屏幕支持
                })
                this.fabricObj.setBackgroundImage(this.imgSrc, this.fabricObj.renderAll.bind(this.fabricObj), {
                    backgroundImageOpacity: 0.5, // should the image be resized to fit the container?
                    backgroundImageStretch: false
                });
                // console.log(this.$parent.canvasHeight)
                this.fabricObj.freeDrawingBrush.color = '#E34F51'
                this.fabricObj.freeDrawingBrush.width = this.drawWidth
                this.fabricObj.setWidth(this.canvasWidth)
                this.fabricObj.setHeight(this.$parent.canvasHeight)
                console.log(this.canvasWidth,this.$parent.canvasHeight)
                this.fabricObj.add(
                // new fabric.Rect({ top: 100, left: 100, width: 50, height: 50, fill: '#f55' }),
                // new fabric.Circle({ top: 140, left: 230, radius: 75, fill: 'green' }),
                // new fabric.Triangle({ top: 300, left: 210, width: 100, height: 100, fill: 'blue' }),
                );
                //綁定畫板事件
                this.fabricObjAddEvent()
                // console.log(this.fabricObj._onSelected)
            },
            //事件監聽
            fabricObjAddEvent() {
                this.fabricObj.on({
                'mouse:down': (o)=> {
                    this.mouseFrom.x = o.pointer.x;
                    this.mouseFrom.y = o.pointer.y;
                    this.doDrawing = true;
                    if(this.currentTool=='text') {
                        this.drawText()
                    }        
                },
                'mouse:up': (o)=> {
                    this.mouseTo.x = o.pointer.x;
                    this.mouseTo.y = o.pointer.y;
                    this.drawingObject = null;
                    this.moveCount = 1;
                    this.doDrawing = false;
                    this.updateModifications(true);
                },
                'mouse:move': (o)=> {
                    if (this.moveCount % 2 && !this.doDrawing) {
                    //減少繪制頻率
                    return;
                    }
                    this.moveCount++;
                    this.mouseTo.x = o.pointer.x;
                    this.mouseTo.y = o.pointer.y;
                    if(this.onStartvalue){
                        this.drawing();
                    }
                },
                //對象移動事件
                'object:moving': (e)=> {
                    e.target.opacity = 0.5;
                },
                'object:added':(e)=>{
                    console.log(e)
                },
                //增加對象
                'object:added': ()=>{
                    // debugger
                },
                'object:modified':(e)=> {
                    e.target.opacity = 1;
                    let object = e.target;
                    this.updateModifications(true)
                },
                'selection:created': (e)=>{
                    console.log(e)
                    if(this.onRemoveVaule){
                        if (e.target._objects) {
                        //多選刪除
                        var etCount = e.target._objects.length;
                        for (var etindex = 0; etindex < etCount; etindex++) {
                            this.fabricObj.remove(e.target._objects[etindex]);
                        }
                        } else {
                        //單選刪除
                        this.fabricObj.remove(e.target);
                        }
                        this.fabricObj.discardActiveObject(); //清除選中框
                        this.updateModifications(true) 
                    }else {
                        // this.onRemoveVaule = false;
                        // this.fabricObj.isDrawingMode = false;
                        // this.fabricObj.skipTargetFind  = false;
                    }
                },
                });
            },
            //儲存歷史記錄
            updateModifications(savehistory) {
                if(savehistory==true) {
                    this.fabricHistoryJson.push(JSON.stringify(this.fabricObj))
                }
            },
            //canvas 歷史后退
            undo() {
                let state = this.fabricHistoryJson
                if(this.mods < state.length) {
                    this.fabricObj.clear().renderAll();
                    this.fabricObj.loadFromJSON(state[state.length - 1 - this.mods - 1]);
                    this.fabricObj.renderAll();
                    this.mods += 1;
                    if(this.mods == state.length){
                        this.fabricObj.setBackgroundImage(this.imgSrc, this.fabricObj.renderAll.bind(this.fabricObj), {
                            // Optionally add an opacity lvl to the image
                            backgroundImageOpacity: 0.5, // should the image be resized to fit the container?
                            backgroundImageStretch: false
                        });

                    }
                }
            },
            //前進
            redo() {
                let state = this.fabricHistoryJson
                if (this.mods > 0) {
                this.fabricObj.clear().renderAll();
                this.fabricObj.loadFromJSON(state[state.length - 1 - this.mods + 1]);
                this.fabricObj.renderAll();
                this.mods -= 1;
                }
            },
            transformMouse(mouseX, mouseY) {
                return { x: mouseX / this.zoom, y: mouseY / this.zoom };
            },
            resetObj() {
                this.fabricObj.selectable = false
                this.fabricObj.selection = false
                this.fabricObj.skipTargetFind = true
                //清除文字對象
                if(this.textboxObj) {
                this.textboxObj.exitEditing();
                this.textboxObj = null;
                }
            },
            handleTools(tools, idx) {
                console.log(tools.name)
                this.initIdx = idx;
                this.currentTool = tools.name;
                this.fabricObj.isDrawingMode = false;
                this.resetObj()
                switch(tools.name) {
                case 'pencil':
                    this.fabricObj.isDrawingMode = true;
                    break;
                case 'remove':
                    this.fabricObj.discardActiveObject().renderAll();  //清除畫布選中元素
                    this.onRemoveVaule = true;
                    this.fabricObj.selection = true
                    this.fabricObj.skipTargetFind = false
                    this.fabricObj.selectable = true
                    break;
                case 'reset':
                    this.onStartvalue = true;
                    this.fabricObj.clear();
                    this.fabricObj.setBackgroundImage(this.imgSrc, this.fabricObj.renderAll.bind(this.fabricObj), {
                        // Optionally add an opacity lvl to the image
                        backgroundImageOpacity: 0.5, // should the image be resized to fit the container?
                        backgroundImageStretch: false
                    });
                    break;
                case 'redo':
                    this.onStartvalue = true;
                    this.redo();
                    break;
                case 'undo':
                    this.onStartvalue = true;
                    this.undo();
                    break;     
                case 'xuxian':
                    this.onStartvalue = true;
                    break;    
                case 'arrow':
                    this.onStartvalue = true;
                    break;    
                case 'line':
                    this.onStartvalue = true;
                    break;  
                case 'juxing':
                    this.onStartvalue = true;
                    break; 
                case 'cricle':
                    this.onStartvalue = true;
                    break; 
                case 'ellipse':
                    this.onStartvalue = true;
                    break; 
                case 'equilateral':
                    this.onStartvalue = true;
                    break; 
                case 'test':
                    this.onSelect()
                    break; 
                case 'baocun':
                    this.downLoadImage()
                    break; 
                default:
                    break; 
                }
            },
            //繪制文字對象
            drawText() {
                this.textboxObj = new fabric.Textbox(" ", {
                    left: this.mouseFrom.x,
                    top: this.mouseFrom.y,
                    width: 220,
                    fontSize: 22,
                    fill: this.drawColor,
                    hasControls: true
                });
                this.fabricObj.add(this.textboxObj);
                this.textboxObj.enterEditing();
                this.textboxObj.hiddenTextarea.focus();
                this.updateModifications(true)
            },
            drawing() {
                // let _this = this;
                if(this.drawingObject) {
                this.fabricObj.remove(this.drawingObject)
                }
                let fabricObject = null
                switch (this.currentTool) {
                case 'pencil':
                    this.fabricObj.isDrawingMode = true
                    break;
                case 'line':
                    fabricObject = new fabric.Line([this.mouseFrom.x,this.mouseFrom.y,this.mouseTo.x,this.mouseTo.y],{
                    stroke: this.drawColor,
                    strokeWidth: this.drawWidth
                    }) 
                    break;
                case 'arrow':
                    fabricObject = new fabric.Path(this.drawArrow(this.mouseFrom.x, this.mouseFrom.y, this.mouseTo.x, this.mouseTo.y, 17.5, 17.5), {
                    stroke: this.drawColor,
                    fill: "rgba(255,255,255,0)",
                    strokeWidth: this.drawWidth
                    });
                    break;
                case 'xuxian': //doshed line
                    fabricObject = new fabric.Line([this.mouseFrom.x, this.mouseFrom.y, this.mouseTo.x, this.mouseTo.y],{
                    strokeDashArray: [10, 3],
                    stroke: this.drawColor,
                    strokeWidth: this.drawWidth
                    })
                    break;
                case 'juxing': //矩形
                    var path =  "M " +
                    this.mouseFrom.x +
                    " " +
                    this.mouseFrom.y +
                    " L " +
                    this.mouseTo.x +
                    " " +
                    this.mouseFrom.y +
                    " L " +
                    this.mouseTo.x +
                    " " +
                    this.mouseTo.y +
                    " L " +
                    this.mouseFrom.x +
                    " " +
                    this.mouseTo.y +
                    " L " +
                    this.mouseFrom.x +
                    " " +
                    this.mouseFrom.y +
                    " z";
                    fabricObject = new fabric.Path(path, {
                    left: this.mouseFrom.x,
                    top: this.mouseFrom.y,
                    stroke: this.drawColor,
                    strokeWidth: this.drawWidth,
                    fill: "rgba(255, 255, 255, 0)"
                    });
                    break;
                case "cricle": //正圓
                    var radius = Math.sqrt((this.mouseTo.x - this.mouseFrom.x) * (this.mouseTo.x - this.mouseFrom.x) + (this.mouseTo.y - this.mouseFrom.y) * (this.mouseTo.y - this.mouseFrom.y)) / 2;
                    fabricObject = new fabric.Circle({
                    left: this.mouseFrom.x,
                    top: this.mouseFrom.y,
                    stroke: this.drawColor,
                    fill: "rgba(255, 255, 255, 0)",
                    radius: radius,
                    strokeWidth: this.drawWidth
                    });
                    break;
                case "ellipse": //橢圓
                    var left = this.mouseFrom.x;
                    var top = this.mouseFrom.y;
                    var ellipse = Math.sqrt((this.mouseTo.x - left) * (this.mouseTo.x - left) + (this.mouseTo.y - top) * (this.mouseTo.y - top)) / 2;
                    fabricObject = new fabric.Ellipse({
                    left: left,
                    top: top,
                    stroke: this.drawColor,
                    fill: "rgba(255, 255, 255, 0)",
                    originX: "center",
                    originY: "center",
                    rx: Math.abs(left - this.mouseTo.x),
                    ry: Math.abs(top - this.mouseTo.y),
                    strokeWidth: this.drawWidth
                    });
                    break;
                case "equilateral": //等邊三角形
                    var height = this.mouseTo.y - this.mouseFrom.y;
                    fabricObject = new fabric.Triangle({
                    top: this.mouseFrom.y,
                    left: this.mouseFrom.x,
                    width: Math.sqrt(Math.pow(height, 2) + Math.pow(height / 2.0, 2)),
                    height: height,
                    stroke: this.drawColor,
                    strokeWidth: this.drawWidth,
                    fill: "rgba(255,255,255,0)"
                    });
                    break;
                case 'remove':
                    break;   
                default:
                    // statements_def'
                    break;
                }
                if(fabricObject) {
                this.fabricObj.add(fabricObject)
                this.drawingObject = fabricObject;
                }
            }, 
            //書寫箭頭的方法
            drawArrow(fromX, fromY, toX, toY, theta, headlen) {
                theta = typeof theta != "undefined" ? theta : 30;
                headlen = typeof theta != "undefined" ? headlen : 10;
                // 計算各角度和對應的P2,P3坐標
                let angle = Math.atan2(fromY - toY, fromX - toX) * 180 / Math.PI,
                angle1 = (angle + theta) * Math.PI / 180,
                angle2 = (angle - theta) * Math.PI / 180,
                topX = headlen * Math.cos(angle1),
                topY = headlen * Math.sin(angle1),
                botX = headlen * Math.cos(angle2),
                botY = headlen * Math.sin(angle2);
                let arrowX = fromX - topX,
                arrowY = fromY - topY;
                let path = " M " + fromX + " " + fromY;
                path += " L " + toX + " " + toY;
                arrowX = toX + topX;
                arrowY = toY + topY;
                path += " M " + arrowX + " " + arrowY;
                path += " L " + toX + " " + toY;
                arrowX = toX + botX;
                arrowY = toY + botY;
                path += " L " + arrowX + " " + arrowY;
                return path;
            },
            downLoadImage() {
                // this.done = true;
                //生成雙倍像素比的圖片
                let base64URl = this.fabricObj.toDataURL({
                    formart: 'png',
                    multiplier: 2
                });
                this.imageBase64 = base64URl;
                this.$emit("onChang", { type: base64URl });
                // this.done = false;
            },
            onSelect(){
                this.onRemoveVaule = false;
                this.onStartvalue = false;
                this.fabricObj.isDrawingMode = false;
                this.fabricObj.skipTargetFind  = false;
            },
            headleChangeColor(value){
                this.drawColor = value;
                console.log(value)
                this.fabricObj.freeDrawingBrush.color = value
            }

        },
    }
</script>

<style lang="scss" scoped>
  .wraper{
    position: relative;
    width: 100%;
    height: 100%;
    .canvas-wraper{
    //   height: 50%;
        height: 100%;
        width: 100%;
        // margin-bottom: 10px;
        overflow: hidden;
    }
    .controlPanel{
        position: absolute;
        width: 800px;
        bottom: 30px;
        // width: 100%;
        // height: 32px;
        // background: #ddd;
        left: 50%;
        transform: translateX(-50%);
        box-shadow: 1px 3px 10px 0 rgba(150, 150, 150, 0.4);
        background: rgba(255, 255, 255, 1);
        border-radius: 5px;
        display: flex;
        flex-direction: column;
        box-sizing: border-box;
        // justify-content: center;
        // align-items: center;
        //   margin-bottom: 15px;
        .text {
            display: inline-block;
            padding-left: 15px;
            font-size: 12px;
            letter-spacing: 1px;
            color: #aaaaaa;
            line-height: 20px;
            border-bottom: 1px solid #eeeeee;
        }
        .tool {
            display: flex;
        }
        .contro-item{
            width: 38px;
            // flex-basis: 100px;
            // border-right: 1px solid #dad7d9;
            text-align: center;
            cursor: pointer;
            background: #fefefe;
            i{
                height: 100%;
                font-size: 22px;
                line-height: 25px;
                display: flex;
                justify-content: center;
                align-items: center;
            }
            &.active{
                // background: #e34f51;
                color: #00BFFF;
                border-radius: 3px;
                i{
                    font-size: 26px;
                }
            }
        }
        .color-list {
            width: 38px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
    }
    .spn {
        display: block;
        width: 20px;
        height: 20px;
    }
    .download{
      img{
        width: 100%;
      }
    }
  }
</style>
<style>
    .controlPanel .box {
        bottom: 0px;
    }
    .wraper .el-slider__button-wrapper {
        z-index: 0 !important;
    }
</style>

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM