摘要:實現(xiàn)原理簡單,純技術(shù)處理圖片,幾乎不需要用到相關知識面向人群急于使用頭像裁剪組件的同學。裁剪框初始寬高上傳圖片后,裁剪區(qū)將預設為最大裁剪范圍。支持矩形裁剪目前九宮僅支持將圖片裁剪為正方形,不能裁剪為矩形,該功能將在后續(xù)優(yōu)化。
項目簡介
本組件是vue下的頭像裁剪組件,可以直接拷貝代碼使用,無需安裝依賴
使用九宮格進行裁剪,自由選擇裁剪區(qū)域。
實時預覽裁剪后效果。
可以將裁剪好的圖片,導出為封裝好的file文件,直接上傳到服務器。
導出圖片鏈接,可以導出為圖片鏈接,直接使用裁剪后的效果。
實現(xiàn)原理簡單,純CSS技術(shù)處理圖片,幾乎不需要用到canvas相關知識
面向人群急于使用vue頭像裁剪組件的同學。直接下載文件,拷貝代碼即可運行。
喜歡看源碼,希望了解組件背后原理的同學。
剛接觸前端的同學也可以通過本文章養(yǎng)成看源碼的習慣。打破對源碼的恐懼,相信自己,其實看源碼并沒有想象中的那么困難
頭像裁剪效果需要解決這么幾個問題
【遮罩鏤空效果】進行裁剪時,是先將原圖添加一層透明的白色遮罩。并在遮罩中顯示一塊清晰的區(qū)塊,作為當前裁剪區(qū)。如何制作該裁剪區(qū)。
【裁剪框初始寬高】上傳圖片后,裁剪區(qū)將預設為最大裁剪范圍。當上傳的圖片非正方形時,如何設置裁剪區(qū)的最大范圍。
【實時預覽】如何將待裁剪區(qū)顯示在實時預覽區(qū)內(nèi)。
【移動范圍約束】移動伸裁剪區(qū)時,如何約束裁剪區(qū)的位置,讓其一直位于原圖上。
【拖拽范圍擴展位置計算】當對裁剪區(qū)進行拉伸時,由于需要保持正方形,拉伸一邊,需要將其臨邊一并拉伸。如何保證拉伸后都不溢出。
實現(xiàn)思路
上傳圖片
通過臨時生成的img標簽獲取圖片的大小信息(getImgSize函數(shù)的具體實現(xiàn)請參考源碼)
比較原圖長和寬,以其長邊作為標準進行圖片的縮放顯示,讓整張圖顯示在待裁剪區(qū)域中
以圖片縮放后的短邊作為選擇框的寬,使裁剪框初始顯示為最大可選范圍
從而解決【裁剪框初始寬高】問題
// 選擇圖片 fileChange(event) { const fileObj = event.target.files[0] const reader = new FileReader() reader.onload = () => { const { selectData, containerBoxData } = this this.imgURL = reader.result this.getImgSize(this.imgURL).then((result) => { // 獲取圖片的大小 if (result.width > result.height) { // 350為外盒子寬高,比較原圖長和寬,以其長邊作為標準進行圖片的縮放顯示 this.scaleRate = 350 / result.width // 獲取并記錄圖片縮放比 containerBoxData.width = 350 containerBoxData.height = Math.floor(result.height * this.scaleRate) selectData.top = 0 selectData.left = (350 - containerBoxData.height) / 2 // 裁剪選擇框居中顯示 selectData.width = containerBoxData.height // 以圖片縮放后的短邊作為選擇框的寬,使其顯示為最大可選范圍 } else { this.scaleRate = 350 / result.height containerBoxData.height = 350 containerBoxData.width = Math.floor(result.width * this.scaleRate) selectData.left = 0 selectData.top = (350 - containerBoxData.width) / 2 selectData.width = containerBoxData.width } this.setPreview() }) } reader.readAsDataURL(fileObj) },
遮罩鏤空的效果實現(xiàn)思路如下
共通過三個元素重疊顯示,原圖img標簽放在最底下,遮罩層.img-mask放在中間,裁剪區(qū)域.select-box放在最外層
.img-mask是遮住了整個img標簽的,只是裁剪區(qū)域.select-box顯示的內(nèi)容剛好與原圖上的一致。實現(xiàn)看起來遮罩被鏤空的效果
其實遮罩并沒有被鏤空,只是遮罩上又多了一層圖案,剛剛與原位置相同
裁剪區(qū)域.select-box,顯示的內(nèi)容和位置,通過background相關屬性,background-position 和 background-size在實現(xiàn)
即選取圖片的特定區(qū)域作為裁剪區(qū)域.select-box的背景
渲染預覽效果
利用canvas的drawImage函數(shù),將候選區(qū)域顯示在預覽區(qū)
drawImage函數(shù)可以設置圖形采取的范圍和位置
并設置采取獲得的圖形,顯示在canvas的那個地方,以多大的size顯示
從而實現(xiàn)采取區(qū),于預覽區(qū)縮放的形式顯示
實現(xiàn)【實時預覽】效果
// 設置預覽圖 setPreview() { const { selectData, scaleRate } = this const $canvas = this.$refs.$canvas.getContext("2d") $canvas.clearRect(0, 0, 190, 190) // 清除canvas中的內(nèi)容 $canvas.drawImage( // 將原圖中,選定區(qū)域的圖案通過canvas渲染出來 this.$refs.$img, // 圖形元素 Math.floor(selectData.left / scaleRate), // 截取原圖中的那個位置作為起始點,X軸方向上 Math.floor(selectData.top / scaleRate), // 截取原圖中的那個位置作為起始點,Y軸方向上 selectData.width / scaleRate, // 截取原圖中多大的范圍,寬度 selectData.width / scaleRate, // 截取原圖中多大的范圍,高度 0, // 顯示在canvas中的X坐標 0, // 顯示在canvas中的Y坐標 190, // 將內(nèi)容伸縮為多大的寬度顯示 190, // 將內(nèi)容伸縮為多大的高度顯示 ) },
拉伸操作的監(jiān)聽
在裁剪區(qū)域中,使用ul,li標簽充當各個操作點,及形成九宮格中的虛線功能
在每個操作點中,監(jiān)聽mousedown事件,記錄即將移動的方向,表示接下來的mousemove事件中,是進行那個方向上的拉伸
在created鉤子中,監(jiān)聽全局 mouseup 和 mousemove 事件。因為鼠標的離開和利用不一樣在裁剪區(qū)域中,在其他地方觸發(fā)也應該同樣執(zhí)行相關操作
在 mousemove 事件中區(qū)裁剪區(qū)移動操作「move」,裁剪區(qū)拉伸操作「stretch」。執(zhí)行不同的函數(shù)
數(shù)據(jù)設置完成后,重新渲染預覽區(qū)
實現(xiàn)【拖拽范圍擴展位置計算】及【拖拽范圍擴展約束】功能
onMouseMove(event) { const { selectData, containerBoxData } = this const { x, y } = selectData.originPoint const moveX = event.clientX - x // X軸移動的距離 const moveY = event.clientY - y // Y軸移動的距離 if (selectData.action === "move") { // 移動選擇框 this.doMove(selectData, containerBoxData, moveX, moveY) } else if (selectData.action === "stretch") { // 拉伸選擇框 this.doStretch(selectData, containerBoxData, moveX, moveY) } else { return } selectData.originPoint = { x: event.clientX > 0 ? event.clientX : 0, y: event.clientY > 0 ? event.clientY : 0, } this.setPreview() },
裁剪區(qū)移動
比較移動后上下左右各個邊與原圖可裁剪區(qū)域的位置關系
若超出范圍,則設置為邊界值
實現(xiàn)【移動范圍約束】功能
// 鼠標移動 doMove(selectData, containerBoxData, moveX, moveY) { selectData.top += moveY selectData.left += moveX if (selectData.top < 0) { selectData.top = 0 } if (selectData.left < 0) { selectData.left = 0 } if (selectData.top + selectData.width > containerBoxData.height) { selectData.top = containerBoxData.height - selectData.width } if (selectData.left + selectData.width > containerBoxData.width) { selectData.left = containerBoxData.width - selectData.width } },
裁剪區(qū)拉伸
設置各個方位具體的拉伸操作
調(diào)用對應函數(shù),先進行一次拉伸操作
拉伸完成后,比較上下左右,寬高的溢出情況,獲得最大的溢出值
以最大溢出值進行反向操作,確保裁剪區(qū)一直在可選范圍內(nèi)
// 選擇框拉伸 doStretch(selectData, containerBoxData, moveX, moveY) { const { minWidth } = this // 比較上下左右,寬高的溢出情況,返回最大的溢出值 function getOverflowLength() { const overflowLeft = selectData.left < 0 ? -selectData.left : 0 const overflowTop = selectData.top < 0 ? -selectData.top : 0 const overflowRight = selectData.left + selectData.width > containerBoxData.width ? selectData.left + selectData.width - containerBoxData.width : 0 const overflowBottom = selectData.top + selectData.width > containerBoxData.height ? selectData.top + selectData.width - containerBoxData.height : 0 const overflowWidth = selectData.width < minWidth ? minWidth - selectData.width : 0 return Math.max(overflowLeft, overflowTop, overflowRight, overflowBottom, overflowWidth) } // 向左拉伸 function doStretchLeft(action) { let space = moveX space = action === "preDo" ? space : -space selectData.top += space / 2 selectData.left += space selectData.width -= space } function doStretchRight(action) { let space = moveX space = action === "preDo" ? space : -space selectData.top -= space / 2 selectData.width += space } function doStretchTop(action) { let space = moveY space = action === "preDo" ? space : -space selectData.top += space selectData.left += space / 2 selectData.width -= space } function doStretchBottom(action) { let space = moveY space = action === "preDo" ? space : -space selectData.left -= space / 2 selectData.width += space } function doStretchTopLeft(action) { let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : moveY space = action === "preDo" ? space : -space selectData.top += space selectData.left += space selectData.width -= space } function doStretchTopRight(action) { let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : -moveY space = action === "preDo" ? space : -space selectData.top -= space selectData.width += space } function doStretchBottomLeft(action) { let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : -moveY space = action === "preDo" ? space : -space selectData.left += space selectData.width -= space } function doStretchBottomRight(action) { let space = Math.abs(moveX) > Math.abs(moveY) ? moveX : moveY space = action === "preDo" ? space : -space selectData.width += space } const doStretchFun = { doStretchLeft, doStretchRight, doStretchTop, doStretchBottom, doStretchTopLeft, doStretchTopRight, doStretchBottomLeft, doStretchBottomRight, }[`doStretch${this.getWord(this.getCamelCase(selectData.direction))}`] // 進行拉伸操作 doStretchFun("preDo") let overflowLength = getOverflowLength() // 拉伸完成后,獲取最大溢出值 if (overflowLength > 0) { // 以最大溢出值進行反向操作,確保裁剪區(qū)一直在可選范圍內(nèi) doStretchFun("reset") } },后續(xù)優(yōu)化
【代碼優(yōu)化】可以看到上面的代碼,有部分冗余,略顯繁瑣。后續(xù)將簡化實現(xiàn)邏輯。
【支持矩形裁剪】目前九宮僅支持將圖片裁剪為正方形,不能裁剪為矩形,該功能將在后續(xù)優(yōu)化。
【原圖的縮放】目前上傳的裁剪目標圖并不支持縮放,上傳大圖時,裁剪顯得不那么靈活,這也將在后續(xù)優(yōu)化
項目源碼及示例 最后送上一張示例中使用的喬巴表情圖文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98137.html
摘要:需求是編寫一個頭像剪裁工具再封裝成為一個組件,然后根據(jù)功能開始逐步編寫代碼先是上傳圖片預覽圖片編輯圖片。 需求是編寫一個頭像剪裁工具再封裝成為一個組件,然后根據(jù)功能開始逐步編寫代碼:先是上傳圖片 => 預覽圖片 => 編輯圖片。 剛開始沒有去考慮兼容性的問題,直接編寫upload部分的代碼,參考了很多代碼還有HTML5 FILE API之后,發(fā)現(xiàn)很少有React編寫的這樣的代碼,因為想...
摘要:兼容性如何支持以及的設備的瀏覽器便可運行不一一列舉一共不到行為什么體積這么小騰訊手內(nèi)大量的都會去不斷地從各個維度進行性能優(yōu)化。騰訊內(nèi)部有哪些項目在用目前主要是興趣部落群等業(yè)務在用,剛剛開源出來,只要有裁剪圖片的地方都會用到。 傳送門 Github地址:https://github.com/AlloyTeam/AlloyFinger/tree/master/alloy_crop 在線De...
摘要:指令中自定義的指令之一,顧名思義,就是當鼠標點擊了指令所綁定元素的外部時,就會觸發(fā)綁定方法。在鼠標放開觸發(fā)事件處理時,通過獲取到他們的對象。使用示例原來的使用方式不受影響,只是添加了多個元素并集作為的功能。指令中的參數(shù)學習 都是個人理解,如果發(fā)現(xiàn)錯誤,懇請大家批評指正,謝謝。還有我說的會比較啰嗦,因為是以自身菜雞水平的視角來記錄學習理解的過程,見諒。 1.前言 產(chǎn)品使用vue+elem...
摘要:目前的技術(shù)棧主要的采用由于是個人項目,所以數(shù)據(jù)請求都是用了代替。后續(xù)會出一系列的教程配套文章,如如何從零構(gòu)建后臺項目框架,如何做完整的用戶系統(tǒng)如權(quán)限驗證,二次登錄等,如何二次開發(fā)組件如富文本,如何整合七牛等等文章,各種后臺開發(fā)經(jīng)驗等等。 完整項目地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎篇)系類文章二:手摸手,帶你用vue擼后臺 系列二...
閱讀 3298·2021-09-08 09:45
閱讀 1255·2019-08-30 15:53
閱讀 1527·2019-08-30 14:12
閱讀 986·2019-08-29 17:01
閱讀 2574·2019-08-29 15:35
閱讀 398·2019-08-29 13:09
閱讀 1975·2019-08-29 12:32
閱讀 3088·2019-08-26 18:37