LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

vue+fabric.js实现简易的图文编辑器

freeflydom
2024年10月25日 10:40 本文热度 117

前言

通过vue2和fabric.js实现一个简易的图文编辑器,可以在画布上添加文字,图片,设置背景图,对文字,图片的属性进行修改。最后生成图片。至于画布上对选中的对象进行拖动,缩放,旋转,这些能力fabric本身已经支持。

1 创建一个vue项目

2 安装fabric.js

建议使用4或5版本,最新版学习成本较高,相关经验文档少。

npm install fabric@4.6.0

3核心代码

页面基本结构

页面左侧为添加元素区域,可添加文字,图片等元素。中间为画布。右侧对选中的元素的属性进行修改。

具体代码可参考源码。项目中我用的node版本是18.17.1。

<div class="editor">

    <div class="sidebar left">

      <button @click="addText">添加文本</button>

      <button @click="addImage">添加图片</button>

      <button @click="setBackgroundImage">添加背景图</button>

    </div>

    <canvas id="c" width="600" height="600" class="canvas"></canvas>

    <div class="sidebar right">

      <!-- 右侧属性面板 -->

       <div v-if="selectedObject">

        属性修改...

       </div>

    </div>

</div>

 

初始化画布

首先确保 页面中已经有canvas标签。

data中定义需要用到到参数

data() {

    return {

      canvas: null,

      selectedObject: null, // 当前选中的元素对象

      canvasWidth: 800, // 初始画布宽度  

      canvasHeight: 600, // 初始画布高度

      canvasBackgroundColor: '#FFF', // 初始画布背景色

    };

},

在mounted钩子函数中创建了Fabric.js画布并监听鼠标点击

创建fabric画布,并指定背景色,大小。

监听mouse:up事件,点击画布上的元素时,更新selectedObject,selectedObject对象表示当前选中元素的属性。

mounted() {

    this.$nextTick(() => {

      this.initCanvas();

    })

},

initCanvas() {

  // 创建画布

  this.canvas = new fabric.Canvas('c',{

    backgroundColor: this.canvasBackgroundColor,

    width: 800,

    height: 576,

  });

  

  // 监听点击

  this.canvas.on('mouse:up', (e) => {

    if (e.target) {

      this.selectedObject = e.target;

    } else {

      this.selectedObject = null

    }

  });

},

添加文本

addText() {

  const text = new fabric.IText('点击编辑', {

    left: 100,

    top: 100,

    fontSize: 30,

    fontFamily: 'arial', // 字体

    fill: '#333', // 颜色

    originX: 'left',

    originY: 'top',

    

  });

  this.canvas.add(text);

},

以上只添加了文本的基本属性,除此之外还有一些常用属性:

editable:是否可编辑,值为布尔值;

lockUniScaling:控制四个正方向缩放,值为布尔值;

lockScalingX: 禁止横向缩放,值为布尔值;

lockScalingY: 禁止纵向缩放,值为布尔值;

同时还可以添加自定义的属性,例如我在添加文本元素时,自定义了属性system_name。

addText() {

  const text = new fabric.IText('当前日期', {

    ......

    system_name: 'current_date',

  });

  this.canvas.add(text);

},

添加图片

addImage() {

  fabric.Image.fromURL('图片url', (img) => {

    img.set({

      left: 100,

      top: 100,

      angle: 0,  // 你可以根据需要调整图片的旋转角度  

    });

    // 将图片添加到画布  

    this.canvas.add(img);

    // 重新渲染画布以显示新添加的图片  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

fabric.Image.fromURL('图片url', callback, options) 方法用于从指定的 URL 加载图片。这个方法接受三个参数。

'图片url':图片的 URL 地址。

callback(img):一个回调函数,当图片加载完成后执行。img 参数是加载后的 Fabric.js 图片对象。

options:一个对象,包含加载图片时的选项。在这个例子中,设置了 { crossOrigin: 'anonymous' },这允许跨域加载图片,避免在加载跨域图片时出现 CORS(跨源资源共享)错误。

如果我希望添加的图片不超出画布,同时居中。上述代码可以这样改进:

计算缩放因子scale并应用,保证图片最长的边不会超出画布。 

addImage() {

  fabric.Image.fromURL('图片url', (img) => {

    // 获取画布的宽高

    const canvasWidth = this.canvas.getWidth();

    const canvasHeight = this.canvas.getHeight();

    const maxWidth = canvasWidth * 0.6; // 计算图片允许的最大宽度  

    const maxHeight = canvasHeight * 0.3; // 计算图片允许的最大高度 

    // 计算缩放比例   

    let scale = Math.min(maxWidth / img.width, maxHeight / img.height);   

    // 应用缩放比例  

    img.scale(scale).set({

      left: (canvasWidth - img.width * scale) / 2, // 居中图片  

      top: (canvasHeight - img.height * scale) / 2,

    });

    // 将图片添加到画布  

    this.canvas.add(img);

    // 重新渲染画布以显示新添加的图片  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

设置背景图

设置背景图有两种实现方式,

1 通过canvas.setBackgroundImage()设置;

2 添加一个图片,宽高与画布大小一致,将其放在所有其他对象的底层,并禁止选中、触发事件。

我这里使用的是第二种方法。

setBackgroundImage(imageUrl) {

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

    // 获取画布的宽度和高度  

    const canvasWidth = this.canvas.getWidth();  

    const canvasHeight = this.canvas.getHeight();  

    // 直接设置图像的宽度和高度为画布的宽度和高度  

    img.set({

      left: 0, 

      top: 0,

      scaleX: canvasWidth / img.width,

      scaleY: canvasHeight / img.height,  

      selectable: false, // 让背景图不可选  

      evented: false,     // 让背景图不触发事件  

      is_background: true // 自定义属性,背景图标识 区别于普通图片元素  

    }); 

    // 将背景图添加到画布上  

    this.canvas.add(img);

    // 将背景图放在所有其他对象的底层  

    this.canvas.sendToBack(img);

    // 重新渲染画布  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

上述方法可以实现设置背景图,但如果我已经设置了背景图,现在又想替换其他背景图时。由于新添加的背景图被放到了最底层,旧的背景图还没删除掉,旧图覆盖在新的背景图上,所以上述代码需要优化,

解决方法:

因为我在添加背景图时自定义了属性is_background,所以每次添加背景图先前遍历画布上的元素,如果is_background属性为true则删除它。然后再添加背景图。完整逻辑如下

setBackgroundImage(imageUrl) {

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

    // 获取画布的宽度和高度  

    const canvasWidth = this.canvas.getWidth();  

    const canvasHeight = this.canvas.getHeight();  

    // 直接设置图像的宽度和高度为画布的宽度和高度  

    img.set({

      left: 0, 

      top: 0,

      scaleX: canvasWidth / img.width,

      scaleY: canvasHeight / img.height,  

      selectable: false, // 让背景图不可选  

      evented: false,     // 让背景图不触发事件  

      is_background: true // 背景图标识  

    }); 

    // 遍历画布上的所有对象,查找并删除已存在的背景图  

    this.canvas.getObjects().forEach((obj) => {  

      if (obj.is_background) {  

        this.canvas.remove(obj);  

      }  

    });

    // 将背景图添加到画布上  

    this.canvas.add(img);

    // 将背景图放在所有其他对象的底层  

    this.canvas.sendToBack(img);

    // 重新渲染画布  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

修改属性

选中元素时,selectedObject表示选中的对象,此时右侧显示相应的属性值修改框。

通过selectedObject.type区分文本或图片。i-text为文本,image为图片

文字常见属性修改:

字体颜色:fill;

字体大小:fontSize;

字体粗细:fontWeight,常规为normal,加粗bold;

字体风格:fontStyle,常规为normal,斜体italic;

下划线:underline,布尔值;

删除线:linethrough,布尔值;

 

<!-- 字体属性修改 -->

<div v-if="selectedObject.type === 'i-text'">

    <div class="style-title">颜色:</div>

    <el-color-picker style="width: 100%;" v-model="selectedObject.fill" @change="updateColor"></el-color-picker>

    <div class="style-title">字体大小:</div>

    <el-input-number  size="small" v-model="selectedObject.fontSize" controls-position="right" @change="canvasRender" :min="1"></el-input-number>

    <div class="style-title">常用属性:</div>

    <!-- 加粗,斜体,下划线,删除线 -->

    <div class="font-style">

      <i class="fa fa-bold" @click="updateTextProps('bold')"></i>

      <i class="fa fa-italic" @click="updateTextProps('italic')"></i>

      <i class="fa fa-underline" @click="updateTextProps('underline')"></i>

      <i class="fa fa-strikethrough" @click="updateTextProps('linethrough')"></i>

    </div>

</div>

......

methods: {

    // 修改颜色

    updateColor(newColor) {

      this.selectedObject.fill = newColor

      this.selectedObject.dirty = true;

      this.canvas.renderAll();

    },

    

    // 渲染画布

    canvasRender(e) {

      this.canvas.renderAll();

    },

    

    // 修改文字属性

    updateTextProps(type) {

      if(type == 'bold') {

      // 加粗

        this.selectedObject.fontWeight = this.selectedObject.fontWeight == 'normal' ? 'bold' : 'normal'

      }

      if(type == 'italic') {

      // 斜体

        this.selectedObject.fontStyle = this.selectedObject.fontStyle == 'normal' ? 'italic' : 'normal'

      }

      if(type == 'linethrough') {

      // 删除线

        this.selectedObject.linethrough = !this.selectedObject.linethrough

      }

      if(type == 'underline') {

      // 下划线

        this.selectedObject.underline = !this.selectedObject.underline

      }

      this.selectedObject.dirty = true;

      this.canvas.renderAll();

    },

}

图片常见属性修改

图片我主要做了尺寸的修改。

 

直接修改width,height并不能改变图片的大小。这是图片本身的物理大小,不能修改的。画布上展现的图片实际大小为物理大小*缩放比,例如宽默认为:width*scaleX,高为height*scaleY。修改图片尺寸,实际上就是修改缩放比scale。因此,在添加图片时,我需要为图片添加两个自定义属性,来表示图片在画布上的实际大小。添加自定义属性scaleWidth,scaleHeight,表示缩放后图片的实际大小。上述添加图片的方法可做以下优化:

addImage() {

  fabric.Image.fromURL('图片url', (img) => {

    ......

    img.scale(scale).set({

      ......

      scaleWidth: img.width * scale,

      scaleHeight: img.height * scale

    });

    ......

  }, { crossOrigin: 'anonymous' });

},

接下来,在右侧属性编辑区修改图片尺寸,实际上要根据修改后的尺寸scaleWidth去计算新的缩放比scaleX,height同理。然后更新图片属性的scaleX,scaleY属性即可。直接用鼠标拖拽图片的边去进行缩放也是在修改scaleX,scaleY。

<!-- 图片属性修改 -->

<div v-if="selectedObject.type === 'image'">

    <div class="style-title">尺寸:</div>  

    <div class="place-line">

      <el-input-number v-model="selectedObject.scaleWidth" size="small" placeholder="宽度" controls-position="right" @change="updateImgScale" :min="1" :max="1000" :precision="0" /> 

      <el-input-number v-model="selectedObject.scaleHeight" size="small" placeholder="高度" controls-position="right" @change="updateImgScale" :min="1" :max="1000" :precision="0" /> 

    </div>

</div>

methods: {

    // 缩放图片

    updateImgScale() { 

      const newScaleX = this.selectedObject.scaleWidth / this.selectedObject.width; 

      const newScaleY = this.selectedObject.scaleHeight / this.selectedObject.height; 

      this.selectedObject.set({

        scaleX: newScaleX,

        scaleY: newScaleY,

      })

      this.canvas.renderAll();

    },

}

删除

将当前选中的对象或对象的集合删除。我这里用到的是getActiveObjects,获取所有选中的对象,遍历并通过canvas.remove()全部删除,如果想获取单个对象,可以用this.canvas.getActiveObject()。

<i class="el-icon-delete" style="color: red;" @click="deleteSelectedObjects"> 

删除</i>

......

// 删除元素

deleteSelectedObjects() {

  const selectedObjects = this.canvas.getActiveObjects();

  if (selectedObjects.length > 0) {

    selectedObjects.forEach(obj => {

      this.canvas.remove(obj);

    });

    this.canvas.renderAll();

    // 清除 selectedObject 引用,如果你需要在其他地方使用它  

    this.selectedObject = null;

  }

},

生成JSON

 

将画布上的所有内容生成JSON文件。通过canvas.toJSON将画布上的图形对象转为JSON对象,生成的JSON对象默认情况下是包含对象的所有属性,但自定义属性我们需要手动指定。this.canvas.toJSON(['属性A', '属性B',...])

<el-button type="primary" size="small" @click="exportCanvasAsJSON">生成JSON</el-button>

......

exportCanvasAsJSON() {

  // 获取画布上所有对象的JSON表示  

  const jsonData = this.canvas.toJSON(['selectable', 'evented', 'is_background', 'scaleX', 'scaleY', 'scaleWidth', 'scaleHeight']); // 你可以根据需要包含或排除属性

  // 将JSON对象转换为字符串  

  const jsonString = JSON.stringify(jsonData, null, 2);

    

  // 以下是下载的逻辑=====================

  // 创建一个Blob对象  

  const blob = new Blob([jsonString], { type: 'text/json' });

  // 创建一个指向blob的URL  

  const url = window.URL.createObjectURL(blob);

  // 创建一个临时的a标签用于下载  

  const a = document.createElement('a');

  a.href = url;

  a.download = 'canvas_data.json'; // 指定下载的文件名  

  document.body.appendChild(a);

  a.click(); // 模拟点击以触发下载  

  // 清理  

  document.body.removeChild(a);

  window.URL.revokeObjectURL(url);

},

生成图片

通过canvas.toDataURL将画布内容导出为数据 URL,并指定为图像格式(如 PNG 或 JPEG)。

<el-button type="primary" size="small" @click="exportCanvasAsImage">下载图片</el-button>

......

exportCanvasAsImage() {

  // 设置图片的质量和格式,这里以PNG格式为例,质量为0.8  

  const imageUrl = this.canvas.toDataURL({  

      format: 'png',  

      quality: 0.8  

  });

  

  // 以下是下载的逻辑=====================

  // 创建一个指向该DataURL的a标签用于下载

  const a = document.createElement('a');

  a.href = imageUrl;

  a.download = 'canvas_image.png'; // 指定下载的文件名

  document.body.appendChild(a);

  a.click(); // 模拟点击以触发下载

  document.body.removeChild(a);

  window.URL.revokeObjectURL(imageUrl);

}

渲染JSON为图像

前面生成的JSON文件,我们通常会保存到本地或传给后端。一般二次编辑时,是需要回显画布的。回显画布可通过canvas.loadFromJSON(json, [callback])来实现。

json (String): 描述画布状态的 JSON 字符串。

callback (Function, 可选): 当 JSON 数据加载完成并渲染到画布上后调用的函数

mounted() {

    this.initData()

    // 如果是编辑时

    if(isEdit) {

      // 请求接口,或读取本地的JSON文件...

      // jsonData为需要渲染的JSON

      this.canvas.loadFromJSON(jsonData, this.canvas.renderAll.bind(this.canvas))

    }

},

​转自https://juejin.cn/post/7427513979639496741



该文章在 2024/10/25 10:41:56 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved