序言
嘿,打工人混跡職場這么久,圖片處理肯定都沒少碰。不過咱說實話,大部分時候都是直接 “抄近道”,用現成的三方組件😏。就像我,主打一個會用工具,畢竟善用工具可是咱人類的 “超能力”,不會用那可就真成 “原始人” 啦🤣。最近在搞微信小程序開發時,遇到個需求:用戶上傳圖片得能裁剪。我瞅了瞅 UI 給的效果圖,好家伙,找遍了都沒發現合適的組件,這下沒辦法,社畜的 “使命感” 上身,只能自己動手,豐衣足食咯😅。
UI圖
咱來拆解一下在微信小程序里實現這功能的流程。首先從上個頁面調用小程序選擇圖片的 API——chooseMedia
,這就好比打開了一個 “圖片寶庫”,通過它能拿到用戶選好圖片后的臨時路徑(下面咱就簡稱 path 啦)。接著帶著這個 path “奔赴” 當前頁面,然后用Canvas
這個神奇的 “畫布” 把圖片畫出來,之后還要讓圖片能實現移動、縮放這些 “炫酷操作”。
使用 Canvas 畫出圖片
選擇圖片這塊咱就不細究啦,不是本文的 “主角”,咱直接從拿到 path 之后講起。
第一步得初始化 Canvas,給它設定寬高。在頁面加載完畢時,利用createSelectorQuery
這個 “小助手”,拿到 Canvas 的寬高以及 node,然后把這些數據存好,后續其他功能計算時可得靠它們 “大顯身手”。
初始化 Canvas
<canvas id="canvas" type="2d" style="width: 100%; height: 100%;"></canvas>
initCanvas() {return new Promise((resolve, reject) => {const query = wx.createSelectorQuery()query.select('#canvas').fields({ node: true, size: true }).exec((res) => {const { node, width, height } = res[0]node.width = widthnode.height = heightconst ctx = node.getContext('2d')this.setData({ 'canvas.target': node,'canvas.ctx': ctx,'canvas.width': width,'canvas.height': height})resolve()})})},
可能有的小伙伴要問了:已經設了寬高 100%,為啥還要再設置 node 呢🧐?這是因為不明確設置寬高屬性,它就會用默認值,這就像你沒給導航設定目的地,它可能就帶你去 “奇怪的地方”,繪圖效果自然就 “跑偏” 了。但 Canvas 尺寸又不是固定不變的,沒法直接簡單設置,所以初始化時這一步很關鍵哦。
初始化圖片到Canvas
接下來把圖片 “請” 到 Canvas 上。注意啦,圖片得在 Canvas 正中間,還得完整地展示在可見區域,可不能按原圖尺寸隨意 “擺放”,所以得做些簡單的 “數學運算”。
initImage() {return new Promise((resolve, reject) => {const { path, canvas: { target: canvas } } = this.datalet image = canvas.createImage();image.src = pathimage.onload = () => {const { ctx, width: canvasWidth, height: canvasHeight } = this.data.canvasconst scale = image.width / image.heightlet width = canvasWidthlet height = canvasWidth / scaleif (height > canvasHeight) {height = canvasHeightwidth = canvasHeight * scale}const x = (canvasWidth - width) / 2;const y = (canvasHeight - height) / 2;ctx.drawImage(image, x, y, width, height);this.setData({'image.target': image,'image.width': width,'image.height': height,'image.x': x,'image.y': y,'image.scale': scale})resolve()}})},
先用 Canvas 對象創建一個圖片對象,再把 path “賦予” 它。等圖片加載好,就能獲取到圖片尺寸,進而算出寬高比。因為要完整展示圖片,所以圖片高度最大就是 Canvas 的高度,寬度最大是 Canvas 的寬度。通過寬高比,就能算出當繪制寬度是 Canvas 寬度時對應的高度。然后把這個計算出的高度和 Canvas 高度對比,如果超了,就以 Canvas 高度為繪制高度,再算出相應寬度。
尺寸確定好后,最后算圖片繪制的中心位置。以 Canvas 左上角為 (0,0),X 軸坐標就是 Canvas 寬度減去圖片寬度再除以 2,Y 軸坐標同理。最后調用drawImage
方法,圖片就 “乖乖” 初始化繪制好啦。別忘了把圖片相關數據存起來,后續功能還得靠它們 “幫忙” 呢。
繪制裁剪區域
再接著就是繪制裁剪區域,從 UI 圖能看出,裁剪區域是正中心的一個長方形。咱先確定好這個長方形尺寸并存起來,然后利用這個尺寸在 Canvas 四周畫上四個黑色遮罩長方形,這招就像 “圍點打援”,簡單又有效😎。
// 裁剪區域的相關數據
crop: {scale: 5 / 7,width: 200,height: 0,x: 0,y: 0,
},
// 裁剪區域尺寸等信息
getCropInfo() {const {canvas: { width: canvasWidth, height: canvasHeight },crop: { width: cropWidth, scale: cropScale }} = this.dataconst result = {scale: cropScale,width: cropWidth,height: cropWidth / cropScale,x: (canvasWidth - cropWidth) / 2,y: (canvasHeight - cropWidth / cropScale) / 2}this.setData({'crop.height': cropWidth / cropScale,'crop.x': (canvasWidth - cropWidth) / 2,'crop.y': (canvasHeight - cropWidth / cropScale) / 2,})return result},// 繪制裁剪區域drawCrop() {const { ctx, width: canvasWidth, height: canvasHeight } = this.data.canvasconst { width: cropWidth, height: cropHeight, x, y} = this.data.crop// 該方式會出現 一次繪制成功一次繪制不成功的情況// ctx.rect(0, 0, canvasWidth, canvasHeight);// ctx.rect(x, y, cropWidth, cropHeight);// ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';// ctx.fill('evenodd');ctx.rect(0, 0, canvasWidth, y);ctx.rect(0, y + cropHeight, canvasWidth, y);ctx.rect(0, y, x, cropHeight);ctx.rect(x + cropWidth, y, x, cropHeight);ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';ctx.fill()},
到這兒,基本雛形已經有啦。不過關于拖拽、縮放這些更精彩的操作,由于篇幅限制,咱下篇再繼續 “揭秘”😜。
🦀🦀感謝看官看到這里,如果覺得文章不錯的話🙌,點個關注不迷路?。
誠邀您加入我的微信技術交流群🎉,群里都是志同道合的開發者👨?💻,大家能一起交流分享摸魚🐟。期待與您在群里相見🚀,咱們攜手在開發路上共同進步? !
👉點我
感謝各位大俠一路相伴,實在感激! 不瞞您說,在下還有幾個開源項目 📦,它們就像精心培育的幼苗 🌱,急需您的澆灌。要是您瞧著還不錯,麻煩動動手指,給它們點亮幾顆 Star ?,您的支持就是它們成長的最大動力,在此謝過各位大俠啦!
Nova UI
組件庫:https://github.com/gmingchen/nova-ui- 基于 Vue3 + Element-plus 管理后臺基礎功能框架
- 預覽:https://admin.gumingchen.icu
- Github:https://github.com/gmingchen/agile-admin
- Gitee:https://gitee.com/shychen/agile-admin
- 基礎版后端:https://github.com/gmingchen/java-spring-boot-admin
- 文檔:http://admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即時聊天系統
- 預覽:https://chatterbox.gumingchen.icu/
- Github:https://github.com/gmingchen/chatterbox
- Gitee:https://gitee.com/shychen/chatterbox
- 基于 node 開發的后端服務:https://github.com/gmingchen/node-server