Vue3使用fabric.js实现可以加载图片与绘制功能的画布

先看效果图

image.png

如图所示,黑色部分为画板区域,中间为图片区域,下面的一排按钮为功能按钮,下面会一一讲解实现步骤

1. 项目初始化

1. 创建Vue3项目
yarn create vite
npm init vite@latest 
pnpm create vite 

创建Vue3项目

2. 输入项目名称

然后输入项目名称

3. 点击键盘上下方向键到Vue,再按回车选择vue

点击键盘上下方向键到Vue,再按回车选择vue

4. 继续按照如上方式选择JavaScript

继续按照如上方式选择JavaScript

5. 打开项目,初始化依赖包

此时项目已经创建完毕,可以通过cd命令进入项目根目录后进行依赖下载

// package.json
{
  "name": "Fabric.js",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "element-plus": "^2.2.26",
    "fabric": "^5.3.0",
    "fabric-with-erasing": "^1.0.1",
    "konva": "^8.4.2",
    "roslib": "^1.3.0",
    "vue": "^3.2.13",
    "vue-konva": "^3.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "@babel/eslint-parser"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead",
    "not ie 11"
  ]
}

// 可以将上面的package.json直接复制过去,然后执行下载依赖
npm install
// 或
yarn

2. 处理main.js,挂载依赖

import { createApp } from 'vue'
import VueKonva from 'vue-konva';
import './index.css'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
createApp(App).use(ElementPlus).use(VueKonva).mount('#app')

3. 项目整体逻辑(后面附有解析)

<template>
  <div>
    <canvas ref="canvasRef" id="canvas" width="800" height="600">123</canvas>
    <el-row type="flex" justify="center" style="margin-top: 50px">
      <input id="setColor" v-model="brushColor" @change="selectColor" type="color" style="opacity: 0" />
      <el-button type="primary" @click="openColorSelect">设置画笔颜色</el-button>
      <el-button :type="paintBrush ? 'primary' : 'default'" @click="openRangeInput">设置画笔粗细</el-button>
      <el-button type="primary" @click.stop="selectEraser(fabricStatus)">{{ fabricStatus ? '使用画笔' : '使用橡皮擦' }}</el-button>
      <el-button :type="paintEraser ? 'primary' : 'default'" @click="openBrushNum">设置橡皮擦粗细</el-button>
      <el-button type="primary" @click="undo()">上一步</el-button>
      <el-button type="primary" @click="redo()">下一步</el-button>
    </el-row>
    <el-row type="flex" justify="center" style="margin-top: 20px">
      <el-form>
        <el-form-item v-if="paintBrush" label="设置画笔粗细">
          <el-slider style="width: 200px" v-model="brushNum" @change="changeBrushNum" />
        </el-form-item>
        <el-form-item v-if="paintEraser" label="设置橡皮擦粗细">
          <el-slider style="width: 200px" v-model="eraser" @change="changeEraserNum" />
        </el-form-item>
      </el-form>
    </el-row>
  </div>
</template>

<script setup>
// 导入fabric第三方库
import { fabric } from 'fabric-with-erasing';
const { onMounted, ref, nextTick } = require('vue-demi');

// 创建canvas实例
const canvasRef = ref();

// 画板中默认显示的图片路径
const showImgSrc = 'https://images.ctfassets.net/hrltx12pl8hq/3E5SSUuJCKt1KyebMAdr7f/6b98ce27789b03a6b4a62092ea4566b6/Group_5_B.jpg?fit=fill&w=600&h=400';

// 画笔颜色
const brushColor = ref('#000');

// 画笔粗细滑块显示/隐藏
const paintBrush = ref(false);

// 画笔粗细
const brushNum = ref(10);

// eraser粗细滑块显示/隐藏
const paintEraser = ref(false);

// 橡皮擦粗细
const eraser = ref(10);

// 当前状态为画笔/橡皮差
const fabricStatus = ref(false);

// 撤销的快照数组,用来记录历史
let undoList = [];

// 恢复的快照数组,用来记录历史
let redoList = [];

// 添加新状态到历史记录中
function saveState() {
  undoList.push(JSON.stringify(canvasRef.value.toDatalessJSON()));
}

// 撤销操作
function undo() {
  // 点击撤销按钮时将undoList中的最后一个画面移动到redoList中
  if (undoList.length !== 0) {
    const last = undoList.pop();
    redoList.push(last);
    // 调用绘画页面的方法
    canvasRef.value.loadFromJSON(last, function () {
      canvasRef.value.renderAll();
    });
  }
}

// 恢复操作
function redo() {
  // 点击撤销按钮时将undoList中的最后一个画面移动到redoList中
  if (redoList.length !== 0) {
    undoList.push(redoList.pop());
    // 调用绘画页面的方法
    canvasRef.value.loadFromJSON(redoList[redoList.length - 1], function () {
      canvasRef.value.renderAll();
    });
  }
}

// 将图片绘制到canvas中
const drawCanvas = () => {
  // 获取canvas元素,fabric将功能应用到此canvas中
  const canvas = document.querySelector('#canvas');

  // 创建一个img实例
  const img = new Image();

  // 设置img的图片地址,后续此图片将会加载进canvas中
  img.src = showImgSrc;

  // 在图片记载完成后进行处理
  img.onload = function () {
    // fabric实例化
    canvasRef.value = new fabric.Canvas(canvas, {
      // 生成的图像宽高,这里的图像宽高为默认的canvas宽高
      width: canvas.width,
      height: canvas.height,
      // 内部图像不允许拖动(因为拖动的动作为画笔动作,两者冲突)
      selection: false,
      // 开启画笔
      isDrawingMode: true,
      // 笔刷高清
      devicePixelRatio: true,
    });

    // 创建图像图层
    let imgLayer = new fabric.Image(img, {
      erasable: false, // 不允许擦拭
      // 设置图像在fabric画板中的位置为水平垂直居中
      left: (canvas.width - img.width) / 2,
      top: (canvas.height - img.height) / 2,
    });

    // 将图片添加到canvas中
    canvasRef.value.add(imgLayer);

    // 监听鼠标按下事件,添加新的状态到历史记录
    canvasRef.value.on('mouse:down', function () {
      saveState();
    });

    // 监听鼠标抬起事件,添加新的状态到历史记录
    canvasRef.value.on('mouse:up', function () {
      saveState();
    });

    // 添加缩放功能
    canvasRef.value.on('mouse:wheel', event => {
      // 获取鼠标的缩放值
      var delta = event.e.deltaY;

      // 获取当前画布的缩放值
      var zoom = canvasRef.value.getZoom();

      // 根据鼠标滚轮的滚动设置画布的缩放值
      zoom *= 0.999 ** delta;

      // 设置缩放值最大为1.5(原先图像的1.5倍)
      if (zoom > 1.5) zoom = 1.5;

      // 设置缩放值最小为0.5(原先图像的0.5倍)
      if (zoom < 0.5) zoom = 0.5;

      // 给定缩放所在的位置以及缩放的大小,画板就会根据给定的位置进行缩放
      canvasRef.value.zoomToPoint({ x: event.e.offsetX, y: event.e.offsetY }, zoom);

      // 阻止默认行为与阻止事件冒泡
      event.e.preventDefault();
      event.e.stopPropagation();
    });
  };
};

// 打开颜色选择画板
const openColorSelect = () => {
  nextTick(() => document.querySelector('#setColor').click());
};

// 修改画笔颜色
const selectColor = ({ target }) => {
  canvasRef.value.freeDrawingBrush.color = target.value;
};

// 切换橡皮擦/画笔状态
const selectEraser = status => {
  // 判断是否为画笔状态
  if (!status) {
    changeAction('erase');
    // 修改为橡皮擦状态
  } else {
    changeAction('undoErasing');
  }
  // 切换为另一个状态
  fabricStatus.value = !fabricStatus.value;
};

// 打开/关闭设置画笔粗细滑块
const openRangeInput = () => {
  paintBrush.value = !paintBrush.value;
};

// 修改画笔粗细
const changeBrushNum = () => {
  canvasRef.value.freeDrawingBrush.width = brushNum.value; // 设置画笔粗细为 10
  changeAction(fabricStatus.value ? 'erase' : 'undoErasing');
};

// 打开/关闭设置橡皮擦粗细滑块
const openBrushNum = () => {
  paintEraser.value = !paintEraser.value;
};

// 修改橡皮擦粗细
const changeEraserNum = () => {
  canvasRef.value.freeDrawingBrush.width = eraser.value; // 设置橡皮擦粗细
  changeAction(fabricStatus.value ? 'erase' : 'undoErasing');
};

// 修改画板行为模式
function changeAction(mode) {
  switch (mode) {
    case 'erase':
      canvasRef.value.freeDrawingBrush = new fabric.EraserBrush(canvasRef.value); // 使用橡皮擦
      canvasRef.value.freeDrawingBrush.width = eraser.value; // 设置橡皮擦粗细
      break;
    case 'undoErasing':
      canvasRef.value.freeDrawingBrush = new fabric.PencilBrush(canvasRef.value); // 使用橡皮擦
      canvasRef.value.freeDrawingBrush.color = brushColor.value; // 使用画笔
      canvasRef.value.freeDrawingBrush.width = brushNum.value; // 设置画笔粗细
    default:
      break;
  }
}

onMounted(() => {
  drawCanvas();
});
</script>

<style></style>

4. 代码解析

1. 将图片绘制到canvas中
// 1. 使用fabric.js新建一个img图层,然后将这个图层添加到已经实例化完毕的fabric实例画板中
// 2. 配置img图层无法被画笔擦除,控制img在画板中的left与top即可实现图片显示在页面的水平垂直区域
let imgLayer = new fabric.Image(img, {
    erasable: false, // 不允许擦拭
    // 设置图像在fabric画板中的位置为水平垂直居中
    left: (canvas.width - img.width) / 2,
    top: (canvas.height - img.height) / 2,
});
// 将图片添加到canvas中
canvasRef.value.add(imgLayer);
2. 画板跟随鼠标缩放
// 1. 画板中监听鼠标的缩放,获取鼠标的缩放值与当前画布的缩放值
// 2. 根据鼠标的缩放值与当前画笔的缩放值处理得到最终的缩放值
// 3. 设置fabric实例缩放值,并且指定缩放值的x,y点与缩放之后的值,即可实现以当前鼠标所在的位置进行缩放
// 添加缩放功能
canvasRef.value.on('mouse:wheel', event => {
  // 获取鼠标的缩放值
  var delta = event.e.deltaY;
  // 获取当前画布的缩放值
  var zoom = canvasRef.value.getZoom();
  // 根据鼠标滚轮的滚动设置画布的缩放值
  zoom *= 0.999 ** delta;
  // 设置缩放值最大为1.5(原先图像的1.5倍)
  if (zoom > 1.5) zoom = 1.5;
  // 设置缩放值最小为0.5(原先图像的0.5倍)
  if (zoom < 0.5) zoom = 0.5;
  // 给定缩放所在的位置以及缩放的大小,画板就会根据给定的位置进行缩放
  canvasRef.value.zoomToPoint({ x: event.e.offsetX, y: event.e.offsetY }, zoom);
  // 阻止默认行为与阻止事件冒泡
  event.e.preventDefault();
  event.e.stopPropagation();
});
3. 修改画板行为模式
// 1. 修改画板行为模式只需要记住两个关键的地方即可(设置画板当前的行为模式为橡皮擦/画笔)
canvasRef.value.freeDrawingBrush = new fabric.EraserBrush(canvasRef.value); // 使用橡皮擦
canvasRef.value.freeDrawingBrush = new fabric.PencilBrush(canvasRef.value); // 使用橡皮擦

// 源代码
// 修改画板行为模式
function changeAction(mode) {
  switch (mode) {
    case 'erase':
      canvasRef.value.freeDrawingBrush = new fabric.EraserBrush(canvasRef.value); // 使用橡皮擦
      canvasRef.value.freeDrawingBrush.width = eraser.value; // 设置橡皮擦粗细
      break;
    case 'undoErasing':
      canvasRef.value.freeDrawingBrush = new fabric.PencilBrush(canvasRef.value); // 使用橡皮擦
      canvasRef.value.freeDrawingBrush.color = brushColor.value; // 使用画笔
      canvasRef.value.freeDrawingBrush.width = brushNum.value; // 设置画笔粗细
    default:
      break;
  }
}
  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值