一個能夠用來截圖的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, 你們討論技術, 不要噴我...

相關文章
相關標籤/搜索