Fabric.js 自由绘制矩形

想知道更多 Fabric.js 的玩法,关注我,第一时间获得最新推送



本文简介

在阅读本文前,你首先需要知道什么是 Fabric.js,还需要知道 Fabric.js 是如何创建矩形的。

如果你还没满足上面2个条件,推荐阅读 《Fabric.js从入门到____》


我在 Fabric.js 使用 框选操作 创建矩形。

接下来的几篇文章我会写如何自由绘制 圆形、椭圆形、三角形、线段、折线、多边形


本文不做任何 CSS 相关的美化,只讲解实现原理。

下图是本文的要实现的效果。

file


使用 Fabric.js 这类框架,是要注意版本的。

本文所用版本: Fabric.js 4.6.0



原理

核心原理

用 “框选” 的方式生成矩形,其核心就2点:

  1. 鼠标 点击抬起 时获取坐标点,也就是 起始点结束点
  2. 鼠标抬起后,第1点 获取到的2个坐标计算出矩形的长、宽和位置。


鼠标点击:canvas.on('mouse:down', fn)

鼠标抬起: canvas.on('mouse:up', fn)


需要考虑的因素

理解了上面的核心点,接下来需要考虑的是鼠标框选的 方向

  1. 左上右下 框选
  2. 右下左上 框选
  3. 左下右上 框选
  4. 右上左下 框选


上面这4种情况会影响生成出来的矩形的 位置


生成矩形的代码

new fabric.Rect({
    top: 0, // 矩形左上角在y轴的位置
    left: 0, // 矩形左上角在x轴的位置
    width: 100, // 矩形的宽
    height: 100, // 矩形的高
    fill: 'transparent', // 填充色
    stroke: '#000' // 边框颜色
})


接下来逐一说说这4种操作带来的影响。


左上右下 框选

file

这种情况是最好处理的。

此时 起始点 就是矩形的左上角,结束点 就是矩形的右下角。

起始点x 和 y 坐标 都小于 结束点,( 起始点x < 结束点x;起始点y < 结束点y ) :

  • 矩形的宽:结束点x坐标 - 起始点x坐标(也可以说是 (起始点x - 结束点x)的绝对值 )。
  • 矩形的高:结束点y坐标 - 起始点y坐标(也可以说是 (起始点y - 结束点y)的绝对值 )。
  • 左上角在x轴的位置:起始点的x轴坐标
  • 左上角在y轴的位置:起始点的y轴坐标


右下左上 框选

file

起始点x > 结束点x; 起始点y > 结束点y

  • 宽:起始点x - 结束点x
  • 高:起始点y - 结束点y
  • 左上角在x轴的坐标:结束点x
  • 左上角在y轴的坐标:结束点y


左下右上 框选

file

起始点x < 结束点x; 起始点y > 结束点y

  • 宽:(起始点x - 结束点y)的绝对值
  • 高:起始点y - 结束点y
  • 左上角在x轴的坐标:起始点x (比较x坐标,取小的那个,可以用 Math.min 方法)。
  • 左上角在y轴的坐标:结束点y (比较y坐标,取小的那个)。


右上左下 框选

file

起始点x > 结束点x; 起始点y < 结束点y

  • 宽:起始点x - 结束点x
  • 高:(起始点y - 结束点y)的绝对值
  • 左上角在x轴的坐标:结束点x (比较x坐标,取小的那个,可以用 Math.min 方法)。
  • 左上角在y轴的坐标:起始点y (比较y坐标,取小的那个)。


总结公式

分析完上面4种情况,最后总结出来这几个参数的公式。

我将 起始点 命名为 downPoint结束点 命名为 upPoint

矩形的几个参数计算公式如下:

new fabric.Rect({
    top: Math.min(downPoint.y, upPoint.y),
    left: Math.min(downPoint.x, upPoint.x),
    width: Math.abs(downPoint.x - upPoint.x),
    height: Math.abs(downPoint.y - upPoint.y),
    fill: 'transparent',
    stroke: '#000'
})


Math.min:两者之中取小值

Math.abs:返回绝对值

这两个都是 JS 提供的方法,如果不理解的建议去百度一下。



动手实现

我在这里贴出用 原生方式 实现的代码和注释。

如果你想知道在 Vue3 环境下如何实现 Fabric.js 自由绘制矩形,可以在 代码仓库 里查找。

<!-- 工具栏 -->
<div class="toolbar">
  <select onchange="typeChange(this.options[this.options.selectedIndex].value)">
    <option value="default">默认(框选)</option>
    <option value="rect">矩形</option>
  </select>
</div>

<!-- 画布 -->
<canvas id="canvas" width="800" height="800"></canvas>

<!-- 引入fabric.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/fabric.js/460/fabric.js"></script>

<script>
let canvas = null // 画布对象

let currentType = 'default' // 当前操作模式(默认 || 创建矩形)

let downPoint = null // 按下鼠标时的坐标
let upPoint = null // 松开鼠标时的坐标

// 初始化画板
function initCanvas() {
  canvas = new fabric.Canvas('canvas')

  canvas.on('mouse:down', canvasMouseDown)   // 鼠标在画布上按下
  canvas.on('mouse:up', canvasMouseUp)       // 鼠标在画布上松开
}

// 画布操作类型切换
function typeChange(opt) {
  currentType = opt
  switch(opt) {
    case 'default': // 默认框选模式
      canvas.selection = true // 允许框选
      canvas.selectionColor = 'rgba(100, 100, 255, 0.3)' // 选框填充色:半透明的蓝色
      canvas.selectionBorderColor = 'rgba(255, 255, 255, 0.3)' // 选框边框颜色:半透明灰色
      canvas.skipTargetFind = false // 允许选中
      break
    case 'rect': // 创建矩形模式
      canvas.selectionColor = 'transparent' // 选框填充色:透明
      canvas.selectionBorderColor = 'rgba(0, 0, 0, 0.2)' // 选框边框颜色:透明度很低的黑色(看上去是灰色)
      canvas.skipTargetFind = true // 禁止选中
      break
  }
}

// 鼠标在画布上按下
function canvasMouseDown(e) {
  // 鼠标左键按下时,将当前坐标 赋值给 downPoint。{x: xxx, y: xxx} 的格式
  downPoint = e.absolutePointer
}

// 鼠标在画布上松开
function canvasMouseUp(e) {
  // 绘制矩形的模式下,才执行下面的代码
  if (currentType === 'rect') {
    // 松开鼠标左键时,将当前坐标 赋值给 upPoint
    upPoint = e.absolutePointer
    // 调用 创建矩形 的方法
    createRect()
  }
}

// 创建矩形
function createRect() {
  // 如果点击和松开鼠标,都是在同一个坐标点,不会生成矩形
  if (JSON.stringify(downPoint) === JSON.stringify(upPoint)) {
    return
  }

  // 创建矩形
  // 矩形参数计算(前面总结的4条公式)
  let top = Math.min(downPoint.y, upPoint.y)
  let left = Math.min(downPoint.x, upPoint.x)
  let width = Math.abs(downPoint.x - upPoint.x)
  let height = Math.abs(downPoint.y - upPoint.y)

  // 矩形对象
  const rect = new fabric.Rect({
    top,
    left,
    width,
    height,
    fill: 'transparent', // 填充色:透明
    stroke: '#000' // 边框颜色:黑色
  })

  // 将矩形添加到画布上
  canvas.add(rect)

  // 创建完矩形,清空 downPoint 和 upPoint。当然,你也可以不做这步。
  downPoint = null
  upPoint = null
}

// 页面加载的生命周期,在此执行 初始化画布 的操作
window.onload = function() {
  initCanvas()
}
</script>



代码仓库



推荐阅读



点赞 + 关注 + 收藏 = 学会了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容