使用Vue + fabric.js构建标注工具的细节

上篇文章大致介绍了使用Vue + fabric.js构建标注工具的流程,本篇则将其中的一些细节以及fabric的踩坑进行补充

1.鼠标从右向左画框

承接上篇的描述,使用fabric在canvas上画标注框的流程主要为:

  1. 监听画布的鼠标按下mouse:down事件,并保存鼠标按下时的坐标,作为标注框的起点(mouseFrom);
  2. 监听画布的鼠标移动mouse:move事件,在鼠标移动过程中,在canvas上绘制以第一步中的起点为左上角,鼠标移动时的坐标为右下角(mouseTo)的矩形(rect);
  3. 监听画布的鼠标抬起mouse:up事件,鼠标抬起时,标注框绘制完毕;
    由此得知,在第二步中的标注框的生成代码为
rect = new fabric.Rect({
    left: mouseFrom.x,
    top: mouseFrom.y,
    width: mouseTo.x - mouseFrom.x,
    height: mouseTo.y - mouseFrom.y
    })

然而这样设置存在一个隐患bug,当鼠标从左向右画框时,标注框正常,但当鼠标从右向左画框时,发现标注框并不能如我们所期望的随着鼠标移动,而是一直向右画框


291891492291361822021-09-02_16.17.20.gif

针对上面场景,一个解决方案为

在绘制框时,先判断mouseFrom.xmouseTo.x,mouseFrom.ymouseTo.y的大小,以较小的那个值为标注框的左上角的坐标(lefttop),以mouseTo.x-mouseFrom.x的绝对值为标注框的宽(width),以mouseTo.y-mouseFrom.y的绝对值为标注框的高(height)

let x = Math.min(mouseFrom.x, mouseTo.x)
let y = Math.min(mouseFrom.y, mouseTo.y)
let width = Math.abs(mouseTo.x-mouseFrom.x)
let height = Math.abs(mouseTo.y-mouseFrom.y)
rect = new fabric.Rect({
    left: x,
    top: y,
    width: width,
    height: height
    })

以这样的方法使得标注框的左上定点是相对小的那个值,虽然rect仍旧是从左画到右,但随着鼠标的移动,视觉上rect是随着鼠标从右向左画

291891492291361822021-09-02_16.44.57.gif

2.标注框溢出画布

  • 绘制过程中标注框溢出画布
    紧接着上步所说的跟随着鼠标移动绘制标注框,当鼠标在画布内的时候,标注框正常绘制,但是,当鼠标移出画布时,mouseFrommouseTo的值仍在变化,但是溢出画布的标注框却不能正常显示,因此在绘制时,需要限制mouseFrommouseTo的值,使得标注框的起点和终点均保持在画布内部。
limitPoint(x,y){
    if(x < 0) x = 0
    if(y < 0) y = 0
    // fabricObj为使用fabric创建的canvas对象,this.fabricObj.getWidth()获取画布的宽
    if(x > this.fabricObj.getWidth()) x = this.fabricObj.getWidth()
    // this.fabricObj.getHeight()获取画布的高
    if(y > this.fabricObj.getHeight()) y = this.fabricObj.getHeight()
}

[图片上传失败...(image-29416e-1630578659128)]

  • 移动标注框过程中溢出画布

canvas.on('object:moving', (e) => {

// 阻止对象移动到画布外面
      let padding = 0; // 内容距离画布的空白宽度,主动设置
      var obj = e.target;
      if (obj.currentHeight > obj.canvas.height - padding * 2 ||
        obj.currentWidth > obj.canvas.width - padding * 2) {
        return;
      }
      obj.setCoords();
      if (obj.getBoundingRect().top < padding || obj.getBoundingRect().left < padding) {
        obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top + padding);
        obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left + padding);
      }
      if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height - padding || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width - padding) {
        obj.top = Math.min(
          obj.top,
          obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top - padding
        );
        obj.left = Math.min(
          obj.left,
          obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left - padding
        );
      }

})

3.屏幕分辨率引起的选中状态下的框移位

在开发过程中,我遇到过这样一个bug,起初在外接显示器上,选中标注框正常,但无意间拖动到自己电脑屏幕上时,诡异的一幕发生了,选中的框跟原本的标注框不对应,再拖回到外接显示器上,又显示正常了

选中状态下选中选中框的八个控制点没有很好的附着在选中框上
看到这个问题,着实让人头疼,明明什么都没动,为啥会出现这样的bug?逐一对比在外接显示器和自己电脑屏幕上console出来的被选中的标注框的各个字段,发现zoomXzoomY在外接显示器上为1,在自己电脑屏幕上为1.25,不由怀疑是zoomXzoomY这两个字段导致的标注框偏移,然后去研究源码,找到在创建标注框rect时zoomXzoomY的赋值逻辑

fabric是通过drawControls()函数绘制选中状态下的控制点的,其中红线框的部分发现设置了transform,紧接着怀疑是canvas的getRetinalScaling()影响到了zoomXzoomY

image.png

image.png

找到getRetinalScaling()的取值函数,发现是根据_isRetinaScaling()函数来决定取fabric.devicePixelRatio还是默认值1,不理解fabric.devicePixelRatio是什么,就接着去找fabric.devicePixelRatio的定义
image.png

window.devicePixelRatio
image.png

到这,恍然大悟,检查自己电脑的分配率设置,果然是125%,与上面所述打印出来的rect的zoomXzoomY对应,试着将分辨率改成100%,发现zoomXzoomY值变为1,选中状态下的控制点也显示正常了

理清bug出现的原因后,自然而然就想到,解决此bug的关键点在于不能让window.devicePixelRatio成为控制点的缩放因子,问题又回到了getRetinalScaling(),如果_isRetinaScaling()为false,那不管屏幕分辨率是多少,getRetinalScaling()值都取1,控制点不就显示正常了?

image.png

然后接着去找_isRetinaScaling()的取值
image.png

发现fabric的canvas有一个enableRetinaScaling参数,默认值为true,官网给出的参数含义为

image.png

单看文档,确实不知所云,但通过源码,很好的就理解了参数的含义,感叹一声,文档还是要配合源码观看效果更佳!

4.选中状态下调整框的等比例缩放问题

开发完之后,产品提出这样一个bug,调整标注框拖动上下左右四个角只能等比例缩放,产品期望能随着鼠标自由地缩放,浏览一遍文档,没有找到对应的设置,那就只能再去源码里面找了,寻找的过程在这里就不啰嗦了,总而言之,通过自下而上地翻阅源码,发现fabric的canvas有一个uniformScaling属性控制着标注框的等比例缩放,且默认值为true,将其设置成false后,bug就迎刃而解了

[图片上传失败...(image-8f96a8-1630578659128)]

5.图片分辨率不同,标注框的宽度设置

由于不同的图片分辨率差异较大,如果以同一种宽度来设置标注框,呈现效果相差较大,因此采取根据图片分辨率来动态设置标注框宽度(scale为上篇文章中创建画布阶段,图片宽高与画布容器宽高的比值)

 <div id="canvax-box">
        <canvas id="label-canvas" :width="width" :height="height">
    </div>
</template>
<script>
 export default{
     methods:{
         fabricCanvas(){
                 ...
                 // 将图片放置在外部容器中
                 let boxWidth = document.getElementById('canvas-box').offsetWidth
                 let boxHeight = document.getElementById('canvas-box').offsetHeight
                 let scaleX = boxWidth / image.width
                 let scaleY = boxHeight / image.height
                 // 确定缩放因子
                 this.scale = scaleX > scaleY ? scaleX : scaleY
                 ...
image.png
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容