一个能够用来截图的 React 组件

灵感

起初只是和同事在去吃路的路上, 互相吹*, 扯一些有的没得, 而后说到了马赛克, 探讨了一下马赛克的实现, 以为还蛮有意思的, 感受能够实现一波, 后面我以为只实现马赛克功能又太单调, 就像作一个相似微信截图的功能.前端

关于组件

实现效果 git

组件

githubgithub

基本效果都是参照微信截图去作的, 可是仍是不同, 微信截图的功能仍是比较通用, 本身还有功能没实现, 后面会说明明细canvas

基本原理

canvas + image 去实现的, 说实话, canvas我用的也不是很熟悉, 只了解基本的api, 可是基本思路仍是明白的, 前面截图大小位置的拖动就是操做dom, 后面对截图的标注之类的都是canvas相关的操做, 一步步实现api

实现细节

最好先看下 github 的 demo, 否则解释起来比较麻烦浏览器

打开一个图片

其实就是展现一个image, 浏览器如何展现这个image参考手机打开一个图片吧, 举个例子, 1980 * 720 的屏幕, 打开 720 * 460 的图片, 图片大小保持不变, 上下左右留白微信

若是打开一个 2800 * 1000 的图片, 应该是宽度撑满, 相似这种dom

反过来, 高度撑满svg

逻辑大概这样性能

const t = image.width / image.height;
if (height < image.height || width < image.width) {
  const ws = image.width / width;
  const hs = image.height / height;

  if (ws <= hs) {
    return [Math.floor(height * t), Math.floor(height)];
  } else {
    return [Math.floor(width), Math.floor(width / t)];
  }
}

return [Math.floor(image.width), Math.floor(image.height)];
复制代码

须要注意的是, canvaswidthstyle.width是两个东西, 我会保留二者的比例, 后面不少操做都须要这个比例

选取截图范围

肯定一个矩形的返回, 只须要知道两个信息, 一个是top left, 一个是 width height, 拖动的时候是分两种状况的, 打个比方, 一个是从左上向右下拖动, top left不变, 只改变width height, 一种是从右下向左上拖动, top left改变, width height 改变, 判断第一个点和拖动的点就能区分这两种状况.而后根据top left width heightcanvas上经过drawImage渲染出截取范围的图片. 还须要注意的是, 也须要给canvas绑定mousemove事件,否则鼠标飘到canvas上的时候就不能拖动了.

此处有个待优化点, 就是当图片尺寸过大时, 拖动不停的drawImage会有一丢丢的卡顿, 这块我后面想优化下, 大概改为这样: 拖动的时候只是控制矩形的border,mouseup的时候再去在canvas上绘画.

拖动点控制大小

截取范围肯定后, 能够经过边角和中间的八个点来控制大小, 这块也是分两种状况的:

  1. 拖动边角的点的行为基本上是一致的
  2. 拖动中间的点的行为差很少, 但有一些差异, 就是控制的是宽度仍是高度的差异

这块个人处理是保存边角的四个点, 拖动边角的行为本质上和咱们选取截图范围的行为是一致的, 由于我保存了四个点, 只要我知道对角的两个点的坐标就能知道怎么去控制这个矩形的变化.

拖动中间的点和边角是很像的, 基本上就是高度不变, 宽度改变, 和宽度改变, 高度不变

拖动改变位置

这个更简单, 就是根据坐标变化计算拖动的距离, 须要注意的就是边界的断定, 由于你不能拖动到外面去了

放大镜的效果

记录鼠标的位置, 根据宽高 drawImage, 须要乘以上面提到的比例, 也须要注意边界的断定.

标注矩形和圆形

这两个放一块儿, 由于这两个的行为基本上是一致的, 只是形状不一样, 刚开始的想法是, 拖动的时候不停clearRect, 再从新drawImage, 这里又有上文提到的问题了, 这里须要把绘图动做存起来, 否则矩形没发保存, 绘图动做越存越多, 拖动的时候会愈来愈卡, 后面我作了优化, 拖动的时候, 展现的实际上是个svg, 等到mouseup的时候再去画 矩形|圆形.

这块有个问题, 感受线条宽度在svgcanvas里面的表现形式有点不同, 这个后面还须要优化

画线

这个比较简单, 记住上一个点就行了, 具体不细说了

马赛克

这个也是蛮有意思的, 由于早就有了思路, 作起来也是比较简单. 打个比方, 画图长宽都为100, 马赛克大小为 10, 我点了坐标(30, 40), 换算就是坐标为(3, 4)的马赛克

// 马赛克大小
const size = 10;
const w = 100;
const h = 100;
const loc = { x: 33, y: 33};
const row = Math.floor(loc.x / size);
const col = Math.floor(loc.y / size);
const index = col * w + row;
const locX = index % w - 1;
const locY = Math.floor(index / w);
// index 是 imageData 的第几个点, 用来填充这个方块
const index = locY * size * w + locX * size;
const r = imgData.data[dataIndex * 4];
const g = imgData.data[dataIndex * 4 + 1];
const b = imgData.data[dataIndex * 4 + 2];
const color = rgb2hex(r, g, b);
context.fillRect(locX * size, locY * size, size, size);
复制代码

上面是比较简单的实现, 实际须要考虑画笔的宽度啥的

打马赛克, 目前比例是 1 的时候正常, 不是 1 的时候, 有点问题, 由于计算的比例通常不可能为整数, 就会致使偏差, 这块我是打算重写, 换种方式去处理, 目前的处理方式仍是以为很差

todo: 箭头

这个有思路, 但暂时没作了, 之前作过移动端对图片的旋转放大, 两个行为基本一致, 想法也是经过 svg 进行处理, 这个后面看状况补上

todo: 标注文字

这个也是有思路, 没作, 感受仍是用操做dom处理比较方便

撤销

撤销我刚开始想偷个懒, 这样处理的, getImageData => 保存 => 撤销 => putImageData. 后面为何改了呢? 一是putImageData性能很差, 二是, 若是是个 8000 * 6000 的图片, 每一个操做我都把这些像素信息存下来, 感受仍是很恐怖的....

后面改为只保存绘画操做, 撤销想当于去掉最后一步操做, 把前面的绘画操做执行一遍就行了

下载

很简单, 转成base64, data-url 的形式, 不作过多说明

拷贝

这个比较麻烦, 原本是想作成拷贝的粘贴板上的, 网上找了半天资料, 最后只作成了选中效果...没啥用的感受, 目前只是作成触发回调的形式.

浏览器拷贝图片到粘贴板应该仍是无法实现, 不知道微信的是怎么处理的, 有了解的同窗能够帮忙科普下

作不了: 拖动

微信的拖动这块感受仍是比较麻烦, 矩形 | 圆 | 线条, 有点思路, 但感受很麻烦, 我选择放弃思考, 有好的点子的同窗能够帮忙提点建议

总结

大概这么多, 涉及到细节基本都一笔带过了, 整体仍是比较有意思的一个组件, 目前还有不少优化的点, 当时只是打草稿写着玩的, 因此如今代码还有点乱.

内容比较简单, 后面看状况补充(有人看的话...)

补充(招聘)

有赞零售正在招前端, 有想法的同窗能够投一份简历给我(shiyangzhaoa@gmail.com), 我知道大家又要说xx, 你们讨论技术, 不要喷我...

相关文章
相关标签/搜索