目录

前言

一、Fabric.js简介

二、开始

1、引入Fabric.js


        演示Demo前端可视化demo

        Demo源码https://gitee.com/k21vin/front-end-data-visualization

        本文章所有的gif图片由于录屏软件问题,上面的鼠标位置都是错位显示的!!

        Fabric.js一个canvas进行封装的Javascript库,在原生canvas之上提供了交互式对象模型通过简洁api可以在画布上进行丰富的操作

        它主要的功能包括在canvas创建填充图形,比如矩形圆形多边形生成图像自带缩放旋转拖拽功能;还可以给图形填充渐变颜色各个图形可以相互组合等等。

npm i fabric --save
// main.js
import fabric from "fabric"
Vue.use(fabric)
<template>
  <canvas id="canvas"></canvas>
</template>

<script>
export default {
  data () {
    canvas: null, // 画布对象
  },
  mounted() {
    this.canvas = new fabric.Canvas('canvas', {
      width: 300,
      height: 200
    })
  }
</script>
canvas.add(object)                   // 添加对象
canvas.remove(object)                // 删除对象
canvas.setWidth(width)               // 设置canvas宽度
canvas.setHeight(height)             // 设置canvas高度
canvas.setDimensions({width, height})// 一键设置宽高
canvas.getObjects()                  // 获取所有对象
canvas.getActiveObject()             // 获取选中对象
canvas.clear()                       // 清除画布中所有对象
canvas.renderAll()                   // 重绘
canvas.requestRenderAll()            // 请求重新渲染
canvas.getZoom()                     // 获取画布当前缩放值
canvas.sendToBack(object)            // 移到对象到最底层
canvas.viewportCenterObjectH(object) // 水平居中对象
canvas.viewportCenterObjectV(object) // 垂直居中对象
canvas.viewportCenterObject(object)  // 垂直水平居中对象
canvas.fxCenterObjectH(object)       // 动画水平居中对象
canvas.fxCenterObjectV(object)       // 动画垂直居中对象
canvas.fxCenterObject(object)        // 动画垂直水平居中对象

let canvasJsonData = JSON.stringify(canvas.toJSON()) // 将画布序列化json数据
let canvasSvgData = canvas.toSVG() // 将画布序列化svg数据
canvas.loadFromJSON(canvasJsonData)  // 反序列化Json数据
mouse: down         // 鼠标按下事件
mouse: move         // 鼠标移动事件
mouse: up           // 鼠标移动事件
mouse: over         // 鼠标移入事件
mouse: out          // 鼠标移出事件
mouse: dblclick     // 鼠标双击事件
object: added       // 对象被添加事件
object: removed     // 对象被删除事件
object: modified    // 对象被修改事件
object: rotating    // 对象被旋转事件
object: scaling     // 对象被缩放事件
object: moving      // 对象被移动事件
canvas.on('mouse: wheel', (opt) => {
  console.log(opt)
})
canvas.off('mouse: wheel')
canvas.selection = true                  // 画布是否选中 默认truefalse不可选中
canvas.selectionColor = 'transparent'    // 画布鼠标框选时的背景色
canvas.selectionBorderColor = 'transparent'// 画布鼠标框选时的边框颜色
canvas.selectionLineWidth = 6            // 画布鼠标框选时的边框厚度
canvas.selectionDashArray = [30, 4, 10] // 画布鼠标框选边框虚线规则
canvas.selectionFullyContained = true   // 只选择完全包含拖动选择矩形中的形状
canvas.backgroundColor = '#2E3136'      // 画布背景色
canvas.hoverCursor = 'pointer'          // 鼠标光标样式 defaultpointer、move等
canvas.skipTargetFind = true            // 整个画板元素不能被选中
canvas.fireRightClick = true            // 启用右键options.button数字为3
canvas.stopContextMenu = true           // 禁止默认右键菜单

        Fabric.js 可以通过 viewportTransform 属性配置画布的视窗属性

canvas.viewportTransform[4] = 100
canvas.viewportTransform[5] = 100

        viewportTransform一个数组里面有6个元素默认值是 [1, 0, 0, 1, 0, 0]。从下标0开始,它们分别代表

[0]: 水平缩放x方向)
[1]: 水平倾斜x方向)
[2]: 垂直倾斜y轴方向)
[3]: 垂直缩放y轴方向)
[4]: 水平移动x轴方向)
[5]: 垂直移动y轴方向)
let circle = new fabric.Circle({
  top: 100,    // y坐标
  left: 100,    // x坐标
  fill: '#17b978', // 填充radius: 50  // 半径
})
rect.top = 100                       // y坐标
rect.left = 100                      // x坐标
rect.width = 100                     // 矩形宽度
rect.height = 100                    // 矩形高度
circle.radius = 50                   // 圆半径
rect.fill = '#17b978'                // 填充rect.stroke = '#FE5332'              // 线条颜色
rect.strokeWidth = 10                // 线条宽度
rect.strokeMiterLimit = index        // 可以用来记录当前选中rectList列表索引!!!!!
circle.set({
  hasBorders: false
}
circle.selectable = false                 // 控件不能被选择,不会被操作
circle.hasControls = false                // 只能移动不能(编辑操作
circle.hasBorders = false                 // 选中时,是否显示边,true:显示(默认circle.borderColor = 'red'                // 选中时,边的颜色
circle.borderScaleFactor = 5              // 选中时,边的粗细
circle.borderDashArray = [20, 5, 10, 7]  // 选中时,虚线边的规则
circle.transparentCorners = false         // 选中时,角是否是空心 true:空心  false:实心
circle.cornerColor = "#a1de93",           // 选中时,角的颜色
circle.cornerStrokeColor = 'pink'         // 选中时,角的边框的颜色
circle.cornerStyle = 'circle'             // 选中时,角的属性  rect:矩形默认)、circle:圆形
circle.cornerSize = 20                    // 选中时,角的大小为20
circle.cornerDashArray = [10, 2, 6]       // 选中时,虚线角的规则
circle.selectionBackgroundColor = '#ffc300' // 选中时,选框背景色
circle.padding = 20                       // 选中时,选框离图形的距离
circle.borderOpacityWhenMoving = 0.6      // 当对象活动和移动时,对象控制边界的不透明度
triangle.perPixelTargetFind = true        // 选择三角形空白位置时候无法选中,false:可以选中(默认
canvas.bringToFront(rect)    // 移到顶层
canvas.sendToBack(rect)      // 移到底层
canvas.bringForward(rect)    // 上移一层
canvas.sendBackwards(rect)   // 下移一层
canvas.moveTo(0)          // 移动指定
handleCopy() {
  if (!canvas.getActiveObject()) {
    this.$message.warning('请先选择元素')
    return
  }
  this.canvas.getActiveObject().clone(cloned => {
    this.cloneObjects = cloned
  })
}
handlePaste() {
  if (!this.cloneObjects) {
    return this.$message.warning('还没复制过任何内容')
  }
  this.cloneObjects.clone(cloned => {
    this.canvas.discardActiveObject() // 取消选择
    // 设置内容的坐标位置
    cloned.set({
      left: cloned.left + 10,
      top: cloned.top + 10,
      evented: true
    })
    if (cloned.type === 'activeSelection') { // 如果复制的是多个对象,则需要遍历克隆对象
      cloned.canvas = this.canvas;
      cloned.forEachObject(obj => {
        this.canvas.add(obj)
      })
      cloned.setCoords()
    } else {
      this.canvas.add(cloned)
    }
    this.cloneObjects.top += 10
    this.cloneObjects.left += 10
    this.canvas.setActiveObject(cloned)
    this.canvas.requestRenderAll()
  })
}

        Fabric对象可以添加一些属性进行锁定例如静止水平移动、静止垂直移动,静止缩放等等


let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ffde7d',
  top: 20,
  left: 20
})
rect.lockMovementX = true
canvas.add(rect)

let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ffde7d',
  top: 20,
  left: 20
})
rect.lockMovementY = true

let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ff9a3c',
  top: 60,
  left: 160
})
rect.lockRotation = true

let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#ffde7d',
  top: 20,
  left: 20
})
rect.lockScalingX = true

let rect = new fabric.Rect({
  width: 100,
  height: 50,
  fill: '#f95959',
  top: 20,
  left: 20
})
rect.lockScalingY = true

let boundingBox = new fabric.Rect({
  top: 100,
  left: 100,
  width: 600,
  height: 400,
  fill: '#f95959',
  selectable: false
})
let movingBox = new fabric.Rect({
  top: 150,
  left: 150,
  width: 100,
  height: 100,
  fill: 'yellow',
  hasBorders: false,
  hasControls: false,
  hoverCursor: 'move'
})
this.canvas.add(boundingBox);
this.canvas.add(movingBox);
this.canvas.on("object:moving", (opt) => {
  let top = movingBox.top;
  let left = movingBox.left;
  let topBound = boundingBox.top;
  let bottomBound = topBound + boundingBox.height;
  let leftBound = boundingBox.left;
  let rightBound = leftBound + boundingBox.width;
  opt.target.left = Math.min(Math.max(left, leftBound), rightBound - movingBox.width)
  opt.target.top = Math.min(Math.max(top, topBound), bottomBound - movingBox.height)
})

        Groups是Fabric最强大的功能之一,它可以将任意数量的Fabric对象组合在一起,形成一个小组分组后,所有对象都可以一起移动、修改缩放旋转甚至更改外观


let group = new fabric.Group([circle, text], {
  left: 100,
  top: 100,
  angle: -10
})
canvas.add(group)

        修改分组的某个对象的属性:


group.item(0).set("fill","red");
group.item(1).set({
  text:"trololo",
  fill:"white"
})

        分组时要记住的另一件事是对象的状态。例如,在与图像组成组时,需要确保这些图像已完全加载


fabric.Image.fromURL(logo, img => {
  let img1 = img.scale(0.3).set({left: 0, top: 0})

  fabric.Image.fromURL(logo, img => {
    let img2 = img.scale(0.3).set({left: 80, top: 0})

    fabric.Image.fromURL(logo, img => {
      let img3 = img.scale(0.3).set({left: 160, top: 0})
      
      let group = new fabric.Group([img1, img2, img3], {
        left: 10,
        top: 400
      })
      canvas.add(group)
    })
  })
})

        每个Fabric对象都有一个animate方法,该方法可以动画化该对象,animate(动画属性,动画结束值,[动画详细信息])


let rect = new fabric.Rect({
  left: 100,
  top: 100,
  width: 100,
  height: 100,
  fill: 'red'
})
rect.animate("angle", 45, {
  onChange: canvas.renderAll.bind(canvas)
})
canvas.add(rect)

        第一个参数是要设置动画的属性。第二个参数是动画的结束值。如果矩形具有-15°的角度,并且我们传递了45,则动画将从-15°变为45°。第三参数一个可选对象,用于指定动画的详细信息持续时间回调,缓动等


rect.animate("angle", 45, {
  from: 0, // 允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)
  duration: 1000, // 默认为500(ms),可用于更改动画的持续时间
  easing: fabric.util.ease.easeOutBounce, // 缓动功能 easeOutBounce、easeInCubic、easeOutCubic、easeInElastic、easeOutElastic、easeInBounce、easeOutExpo
  onChange: canvas.renderAll.bind(canvas), // 在每次刷新时都会执行
  onComplete: (e) => { console.log(e) } // 在动画结束调用回调
})

        animate的一个方便之处在于它还支持相对值


// 向右移动100px
rect.animate('left', '+=100', {
  onChange: canvas.renderAll.bind(canvas)
})

// 逆时针旋转5度
rect.animate('angle', '-=5', {
  onChange: canvas.renderAll.bind(canvas)
})

        fabric.Image的每个实例具有filters”属性,该属性是一个简单过滤器数组。该阵列中每个过滤器都是Fabric过滤器之一的实例。或您自己自定义过滤器的实例


let url = 'http://localhost:82/public/img/5.png' // 图片url
let base64Url = await this.imgUrlToBase64(url) // 图片base64 url
// 正常照片
fabric.Image.fromURL(url, img => {
  img.scale(0.3)
  canvas.add(img)
})

// 单个滤镜
fabric.Image.fromURL(base64Url, img => {
  img.scale(0.3)
  img.left = 300
  // 添加滤镜
  img.filters.push(new fabric.Image.filters.Grayscale())
  // 图片加载完成之后,应用滤镜效果
  img.applyFilters()
  canvas.add(img)
})

// 叠加滤镜
fabric.Image.fromURL(base64Url, img => {
  img.scale(0.3)
  img.set({
    left: 300,
    top: 250,
  })
  img.filters.push(
    new fabric.Image.filters.Grayscale(),
    new fabric.Image.filters.Sepia(), //色偏
    new fabric.Image.filters.Brightness({ brightness: 0.2 }) //亮度
  )
  img.applyFilters()
  canvas.add(img)
})

        (说明这里图片可能出错,放本地图片地址会报“Cannot read propertynaturalWidth’ of null”的错误直接网络图地址会报“Failed to execute ‘texImage2D’ on ‘WebGLRenderingContext‘: The image element contains crossorigin data, and may not be loaded.”的错误解决方法就是将转为Base64格式

        Fabric支持在所有对象上设置填充描边属性的渐变,首先创建渐变然后将其分配填充描边


// 圆
let circle = new fabric.Circle({
  left: 100,
  top: 100,
  radius: 50,
})
let gradient = new fabric.Gradient({
  type: 'linear', // linear or radial
  gradientUnits: 'pixels', // pixels or pencentage 像素 或者 百分比
  coords: { x1: 0, y1: 0, x2: circle1.width, y2: 0 }, // 至少2个坐标对(x1,y1和x2,y2)将定义渐变在对象上的扩展方式
  colorStops:[ // 定义渐变颜色的数组
    { offset: 0, color: 'red' },
    { offset: 0.2, color: 'orange' },
    { offset: 0.4, color: 'yellow' },
    { offset: 0.6, color: 'green' },
    { offset: 0.8, color: 'blue' },
    { offset: 1, color: 'purple' },
  ]
})
circle.set('fill', gradient);
canvas.add(circle)

let circle = new fabric.Circle({
  left: 100,
  top: 100,
  radius: 50
})
let gradient = new fabric.Gradient({
  type: 'radial',
  coords: {
    r1: 50,
    r2: 0,
    x1: 50,
    y1: 50,
    x2: 50,
    y2: 50,
  },
  colorStops: [
    { offset: 0, color: '#fee140' },
    { offset: 1, color: '#fa709a' }
  ]
})
circle.set('fill', gradient);
canvas.add(circle)

<script>
  export default {
    data() {
      return {
        lastPosX: 0,       // 上次鼠标位置X坐标
        lastPosY: 0,       // 上次鼠标位置Y坐标
        isDragging: false, // 是否可以拖拽画布
      }
    },
    mounted() {
      ... // 初始化canvas
      this.canvas.on('mouse:down', this.onMouseDown)
      this.canvas.on('mouse:move', this.onMouseMove)
      this.canvas.on('mouse:up', this.onMouseUp)
    },
    methods: {
      // 监听鼠标按下事件
      onMouseDown(opt) {
        this.lastPosX = opt.e.clientX
        this.lastPosY = opt.e.clientY
        this.isDragging = true
      },
      // 监听鼠标移动事件
      onMouseMove(opt) {
        if (this.isDragging) {
          this.canvas.viewportTransform[4] += opt.e.clientX - this.lastPosX
          this.canvas.viewportTransform[5] += opt.e.clientY - this.lastPosY
          this.canvas.requestRenderAll()
          this.lastPosX = opt.e.clientX
          this.lastPosY = opt.e.clientY
        }
      },
      // 监听鼠标松开事件
      onMouseUp(opt) {
        if (this.isDragging) {
          this.canvas.setViewportTransform(this.canvas.viewportTransform)
          this.isDragging = false
        }
      }
    }
  }
</script>

this.canvas.on('mouse:wheel', this.onMouseWheel)

// 监听鼠标放大缩小事件
onMouseWheel(opt) {
  let delta = opt.e.deltaY // 滚轮向上滚一下是 -100,向下滚一下是 100
  let zoom = this.canvas.getZoom() // 获取画布当前缩放值
  zoom *= 0.999 ** delta
  zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
  this.canvas.zoomToPoint({ // 以鼠标指针位置为基准缩放
    x: opt.e.offsetX,
    y: opt.e.offsetY
  }, zoom)
  opt.e.preventDefault()
  opt.e.stopPropagation()
}

let canvas = new fabric.Canvas('canvas', {
  isDrawingMode: true // 开启绘图模式
})
canvas.freeDrawingBrush.color = '#11999e' // 设置画笔颜色
canvas.freeDrawingBrush.width = 10 // 设置画笔粗细
canvas.freeDrawingBrush.shadow = new fabric.Shadow({ // 设置画笔投影
  blur: 10,
  offsetX: 10,
  offsetY: 10,
  affectStroke: true,
  color: '#30e3ca'
})

canvas.isDrawingMode = false

<img src="@/assets/images/logo.png" id="logo">

let img = document.getElementById('logo')
img.onload = () => {
  let canvasImage = new fabric.Image(imgElement, {
    left: 100, // 距离画布左侧距离
    top: 100, // 距离画布顶部距离
    width: 200, // 图片宽度
    height: 200, // 图片高度
    angle: 50, // 旋转
    opacity: 1 // 透明度
  })
  canvas.add(canvasImage)
}

let url = 'http://localhost:82/public/img/logo.png'
fabric.Image.fromURL(url, img => {
  let canvasImage = img.set({
    scaleX: 0.5,
    scaleY: 0.5
  })
  canvas.add(canvasImage)
}) 

        Fabric也提供了文本相关功能,Fabric文本允许以面向对象方式处理文本原生canvas方法,只允许在非常低的级别上填充或描边文本,通过实例化fabric.Text实例,我们就可以使用文本,就像我们将使用任何其他Fabric对象:移动它,缩放它,更改其属性等, 其次它提供比canvas给我们更丰富的功能,包括:


Multiline support   // 支持多行
Text alignment      // 文本对齐 Left、centerright
Text background     // 文本背景 背景也遵循文本对齐
Text decoration     // 文字装饰 下划线Underline、上划线overline、贯穿线strike-through
Line height         // 行高 使用多行文字时出错
Char spacing        // 字符间距 使文本更紧凑或间距更大
Subranges           // 子范围 将颜色和属性应用于文本对象的子范围
Multibyte           // 多字节 支持表情符号
On canvas editing   // 交互式画布编辑 可以直接在canvas上键入文本

let text = new fabric.Text('Hello World!', {
  left: 40,
  top: 10,
  fontFamily: 'Comic Sans', // 字体
  fontSize: 60, // 字号
  fontWeight: 600, // 字体重量(粗细),normal、bold 或 数字(100、200、400、600、800)
  fontStyle: 'normal', // 字体风格 正常 normal斜体 italic
  charSpacing: 100, // 字距
  fill: 'red', // 字体颜色
  cornerColor: 'pink', // 角的颜色(被选中时)
  angle: 30, // 旋转
  backgroundColor: '#ffd460', // 背景色
  borderColor: 'yellowGreen', // 边框颜色(被选中时)
  borderScaleFactor: 4, // 边框粗细(被选中时)
  borderDashArray: [10, 4, 20], // 创建边框虚线
  stroke: '#3f72af', // 文字描边颜色(蓝色)
  strokeWidth: 2, // 文字描边粗细
  textAlign: 'left', // 对齐方式:left 左对齐; right 右对齐; center 居中
  opacity: 0.8, // 不透明度
  // text: '雷猴', // 文字内容,会覆盖之前设置的值
  selectable: true, // 能否被选中,默认true
  shadow: 'rgba(0, 0, 0, 0.5) 5px 5px 5px', // 投影
})
canvas.add(text)

// 下划线
let underlineText = new fabric.Text("I am an undrline text", {
  underline: true
})
canvas.add(underlineText)

// 贯穿线
let strokeThroughText = new fabric.Text("I am a stroke-through text", {
  linethrough: true,
  top: 40
})
canvas.add(strokeThroughText)

// 上划线
let overlineText = new fabric.Text("I am overline text", {
  overline:true,
  top: 80
})
canvas.add(overlineText)

let IText = new fabric.IText('雷猴啊,双击打几个字试下~', {
  fontFamily: 'Comic Sans'
})
canvas.add(IText)

let line = new fabric.Line([0, 100, 100, 100], {
  fill: 'green', // 填充色
  stroke: 'green', // 笔触颜色
  strokeWidth: 2, // 笔触宽度
});
canvas.add(line);

        在绘制直线的基础上添加属性strokeDashArray[a,b],表示每隔a个像素空b个像素


let line = new fabric.Line([0, 100, 100, 100], {
  fill: 'green', // 填充色
  stroke: 'green', // 笔触颜色
  strokeWidth: 2, // 笔触宽度
  strokeDashArray:[3,1]
});
canvas.add(line);

let path = new fabric.Path('M 0 0 L 200 100 L 170 200 z')
path.set({
    top: 120, // 距离容器顶部距离 120px
    left: 120, // 距离容器左侧距离 120px
    fill: 'hotpink', // 填充 亮粉色
    opacity: 0.5, // 不透明度 50%
    stroke: 'black', // 描边颜色 黑色
    strokeWidth: 10 // 描边粗细 10px
})

        上述代码第一行“M”代表“移动”命令,“M 0 0” 代表画笔移动到(0, 0)点坐标。“L”代表“线”,“L 200 100 ”的意思是使用钢笔画一条线,从(0, 0)坐标画到(200, 100)坐标。“z” 代表让图形闭合路径。这样就画出了一个三角形。画好三角形后,我们可以用set( )方法三角形的位置、颜色、角度、透明度等属性进行设置。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注