制作图片涂鸦画板(微信浏览器)

892 阅读5分钟
  1. fabric+customiseControls简介

fabric是一个canvas库,其核心就是将canvasAPI转换为对象模型,让我们更加便捷的使用。

customiseControls则是基于fabric的插件,可为新建的fabric对象设置角标,并为其提供一系列事件:drag,

scale,rotate,remove,或者自定义事件。

fabric:github.com/fabricjs/fa…

customiseControls:github.com/pixolith/fa…

  1. 效果说明(模糊的图片是故意的)
  • 用户涂鸦环节:

1.png

涂鸦的构成 = 1.城市背景图 + 贴纸图片素材 + 文字图片素材

1点击缩略图切换,切换时2,3不变化。 2,3是可选中后可拖拽,拖拽时不影响其他图片素材,选中之后出现四个角的角标,角标的事件为放大,缩小,旋转,移除。

  • 生成环节

2.png

根据用户涂鸦的结果,生成一张带有其他信息的海报(长按保存到手机相册),比如: 用户地理位置信息,当前参与人数,二维码,昵称,头像等等

  1. 开撸

开撸之前还是梳理一下重点和优化点:

A.涂鸦画板的交互(重点)

B.精准生成海报(重点)和海报的清晰度(优化点)

A部分: 用户选择对应背景或者涂鸦素材后有正确相应,设置边界值

1.初始化fabric插件

// 设置部分角标不可见
fabric.Object.prototype.setControlsVisibility({
  ml: false,
  mr: false,
  mtr: false,
  mt: false,
  mb: false
});

// 角标事件设置
fabric.Canvas.prototype.customiseControls({
  br: {
    action: "rotate"
  },
  bl: {
    action: 'scale'
  },
  tl: {
    action: function action(e, target) {
      var canvas = target.canvas;
      canvas.remove(target).renderAll();
    }
  },
  tr: {
    action: 'scale'
  }
});

// 禁用选框
fabric.Object.prototype.transparentCorners = false;

// 角标基础设置
fabric.Object.prototype.customiseCornerIcons({
  settings: {
    borderColor: '#fff',
    cornerSize: 20,
    cornerBackgroundColor: 'transparent',
    cornerPadding: 5
  },
  br: {
    action: "rotate",
    icon: './imgs/icons/rotate.svg'
  },
  bl: {
    action: 'scale',
    icon: './imgs/icons/resize.svg'
  },
  tl: {
    icon: './imgs/icons/remove.svg',
    action: function action(e, target) {
      var canvas = target.canvas;
      canvas.remove(target).renderAll();
    }
  },
  tr: {
    action: 'scale',
    icon: './imgs/icons/resize.svg'
  }
}, function () {
  board.renderAll();
});
var ww = $(window).width();
var wh = $(window).height();
var board = new fabric.Canvas('drawBoard', {
  backgroundColor: 'transparent',
  width: (ww * 0.92
  height: (wh * 0.67
});

2.添加用户点击交互(click -> 新增对应素材至画板 或者切换城市背景)

var fabricItemList = [];

// 设置图片背景

function setImagebg(imgSrc) {
  fabric.Image.fromURL(imgSrc, function (img) {
    img.set({
      scaleX: board.width / img.width,
      scaleY: board.height / img.height
    });
    board.setBackgroundImage(img, board.renderAll.bind(board));
    board.requestRenderAll();
  }, {
    crossOrigin: 'anonymous'
  });
}

// 设置自定义图片

function setPicture(obj) {
  obj.customiseCornerIcons(function () {
    board.renderAll();
  });
  fabricItemList.push(obj);
  board.add(obj);
  board.setActiveObject(obj);
}

// 生成文字对象

function addText(ctext) {
  var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "#fff";
  var textObj = new fabric.Text(ctext, {
    fontFamily: "PingFang SC, Verdana, Helvetica Neue, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif",
    fontSize: 30,
    top: 150,
    left: 150,
    originX: 'center',
    originY: 'center',
    fill: color
  });
  setPicture(textObj);
}

// 生成图片对象

function featImg(imgSrc, scale) {
  fabric.Image.fromURL(imgSrc, function (img) {
    img.set({
      scaleX: scale,
      scaleY: scale,
      angle: 0,
      left: Math.random() < 0.5 ? Math.random() * 50 + 110 : -Math.random() * 50 + 110,
      top: Math.random() < 0.5 ? Math.random() * 50 + 120 : -Math.random() * 50 + 120,
      hasControls: true,
      borderColor: '#fff'
    });
    setPicture(img);
  }, {
    crossOrigin: 'anonymous'
  });
}

3.生成画板图

let drawImg = board.toDataURL();   

// 生成后复原fabric对象
fabricItemList.forEach(function (item) {
    return board.remove(item);
});
fabricItemList = [];

B部分: 如何根据设计稿精准绘制海报(重点)?

1.使用现有成熟的方案: canvas2Image + html2canvas(高质量前端快照方案:来自页面的「自拍」)

详见网易云音乐团队的文章:juejin.cn/post/684490…

2.自己瞎折腾

最终选择:**自己瞎折腾 **

心路历程:

????作者是不是脑子有病(小声BB)...

为什么我要做费力不讨好的事情? 现成的库他不香吗?

原因在于ios手机在授权后的:微信浏览器的白条问题

白条会使得整个可视区的DOM被向上顶一部分距离...

整个网上的方案都在现有资源下都不能好好解决,developers.weixin.qq.com/community/d…

白条问题使得在使用前面的「自拍」方案时,生成的海报会错位...结果惨不忍睹!

html2canvas是根据遍历DOM样式信息来生成的,而且在遍历过程中,它会重新加载所有资源(在network自己看),如果遇到质量较高的图片,会出现绘制空白的可能。

自己瞎折腾的结果:

根据canvas的drawimage写了个生成图的js,写的垃圾,大佬勿喷... github.com/tsunamiGG/u…

大概思路:

canvas图层堆叠是有顺序的,所以按照图层顺序进行传参。

为保证生成资源的完整性,必须先确保onload之后进入合成过程。

以左上角为原点,然后使用性能不错的drawimage(x,y,w,h)离屏渲染生成,从而避开了生成图被白条影响。

b部分:海报的清晰度(优化点)

可以看到上面的效果图清晰度是非常不理想的,需要优化。


为什么我们的canvas绘制的东西看上去总是比用DOM合成的东西糊一些?

真实物理设备的像素和css的像素是有差距的,window.devicePixelRatio的值就反应了其比例。它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素。

同一个东西,你使用一个css像素来表示,假设在不同手机上分别浏览器本需要4个物理像素或8个物理像素。而canvas只用一个位图像素来填充4个物理像素或8个物理像素,不糊才怪呢..

如果想深入了解,参考:juejin.cn/post/684490…


1.js设置放大devicePixelRatio倍数宽高的canvas,css设置缩小devicePixelRatio倍数的canvas。扩大实际像素数量,视觉上保持跟原有尺寸一致,在API生成图片后会清晰很多。

2.关闭canvas抗锯齿

const canvas = document.createElement("canvas");
const scale = window.devicePixelRatio;
canvas.width = width * scale;
canvas.height = height * scale;
const context = canvas.getContext("2d").scale(scale, scale)


// 关闭抗锯齿
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;

优化后效果如下,手机保存图实际质量比这个高:

3.png

4.总结

对十分陌生的canvas稍微有了一些认识,用canvas做动画的库非常多,但有个致命的缺点就是没办法进行调试...

其中还有很多微信浏览器的很多玄学问题就不展开了,白条问题确实是很头疼的。fabric可以整出很多惊艳的效果,但其乱七八糟的文档和糟糕的示例会带来很多麻烦,影响发挥...