示例簡介
本文介紹使用微信小程序API canvas來實(shí)現(xiàn)圖片的可拖動(dòng)、放大、縮小和旋轉(zhuǎn),并可對選中的圖片進(jìn)行不同效果的濾鏡和不同形狀的切圖,且可對最終效果進(jìn)行保存到本地。
實(shí)現(xiàn)過程
1、文件index.wxml和index.wxss代碼如下,這一塊比較簡單,可自行查看,不做過多分析:
<view class='contentWarp'> <block wx:for="{{itemList}}" wx:key="id"> <view class='touchWrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; z-index:{{item.active?100:1}}'> <view class='imgWrap {{item.active? "touchActive":""}}' style="transform: rotate({{item.angle}}deg); border: {{item.active?4*item.oScale:0}}rpx #fff dashed;"> <image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='WraptouchStart' bindtouchmove='WraptouchMove' bindtouchend='WraptouchEnd' mode="widthFix"></image> <image class='x' src='../../images/del.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtap='deleteItem'></image> <image class='o' src='../../images/scale.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='oTouchStart' bindtouchmove='oTouchMove' bindtouchend='WraptouchEnd'></image> </view> </view> </block></view><!-- 右下角操作按鈕 --><view class="operation-buttons"> <image src="../../images/upload.png" bindtap="uploadImg"></image> <image src="../../images/fliters.png" bindtap="toggleFliters"></image> <image src="../../images/shapes.png" bindtap="toggleShapes"></image> <image src="../../images/synthesis.png" bindtap="openMask"></image></view><!-- 各種過濾效果 --><view class="fliters" hidden="{{!showFliters}}"> <block wx:for="{{fliters}}" wx:key="id"> <image data-fliter="{{item.fliter}}" src="{{item.src}}" bindtap="imgEffects"></image> </block></view><!-- 各種形狀 --><view class="shapes" hidden="{{!showShapes}}"> <block wx:for="{{shapes}}" wx:key="id"> <image data-shape="{{item.shape}}" src="{{item.src}}" bindtap="imgEffects"></image> </block></view><!-- 保存顯示效果圖 --><view class='canvasWrap' hidden="{{!showCanvas}}"> <image class="resImg" bindlongtap="saveImg" src="{{canvasTemImg}}" mode="widthFix"></image> <view class='btn_view'> <button bindtap='saveImg'>保存到手機(jī)</button> <button bindtap='disappearCanvas'>關(guān)閉</button> </view></view><!-- 畫布 --><canvas class='maskCanvas' canvas-id="maskCanvas" style='width:{{canvasWidth}}px; height:{{canvasHeight}}px;'></canvas>
/**index.wxss**/.contentWarp { position: absolute; width: 100%; height: 100%; top: 0; left: 0; bottom: 0; right: 0; margin: auto;}.touchWrap { transform-origin: center; position: absolute; z-index: 100;}.imgWrap { box-sizing: border-box; width: 100%; transform-origin: center; float: left; border: 5rpx transparent dashed;}.imgWrap image { float: left;}.touchActive .x { display: block;}.touchActive .o { display: block;}.x { position: absolute; top: -25rpx; left: -25rpx; z-index: 500; display: none; width: 50rpx; height: 50rpx; overflow: hidden; font-weight: bold; color: #d1e3f1;}.o { position: absolute; bottom: -25rpx; right: -25rpx; width: 50rpx; height: 50rpx; text-align: center; display: none; overflow: hidden; font-weight: bold; color: #d1e3f1;}.active { background-color: rgb(78, 114, 151);}.active view { border: none;}.touchActive { z-index: 400;}.operation-buttons { position: absolute; bottom: 100rpx; right: 20rpx; display: flex; flex-direction: column; z-index: 101;}.operation-buttons image { width: 100rpx; height: 100rpx; margin-top: 40rpx;}.canvasWrap { position: absolute; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.6); z-index: 999; text-align: center;}.maskCanvas { position: absolute; left: -200%; top: 0;}.btnView view { padding-bottom: 20rpx;}.hand { position: absolute; left: 100rpx; right: 0; margin: auto; z-index: 100;}.getUserInfoBtn { position: initial; border: none; background-color: none;}.getUserInfoBtn::after { border: none;}.btn_view { display: flex; padding: 20rpx;}.btn_view button { width: 210rpx; font-size: 28rpx; background-color: #eb4985; color: #fff; line-height: 80rpx;}.resImg { width: 75%; margin-top: 10px;}/* 特效樣式 */.fliters { display: flex; flex-direction: column; position: absolute; bottom: 382rpx; right: 120rpx; z-index: 201;}.shapes { display: flex; flex-direction: column; position: absolute; bottom: 242rpx; right: 120rpx; z-index: 201;}.fliters image, .shapes image { width: 60rpx; height: 60rpx; border: 2rpx solid #eb4985;}
2、文件index.js存放所有功能的邏輯代碼,相對比較復(fù)雜,下面分開來分析幾個(gè)重點(diǎn)方法:
1)方法uploadImg setDropItem:獲取上傳圖片的信息,跟設(shè)置的最大寬高進(jìn)行判斷(maxWidth, maxHeight),然后根據(jù)判斷的結(jié)果進(jìn)行縮放,避免大圖溢出,且設(shè)置圖片的地址、坐標(biāo)、定位和是否選中等信息;用于后面功能使用,支持多圖使用;
2)方法WraptouchStart WraptouchMove:獲取圖片移動(dòng)坐標(biāo)和觸發(fā)時(shí)坐標(biāo)的差值,加上圖片本來的坐標(biāo)來實(shí)現(xiàn)移動(dòng)效果,注意要把移動(dòng)坐標(biāo)賦值給觸發(fā)時(shí)坐標(biāo)(items[index].lx = e.touches[0].clientX),不然會(huì)導(dǎo)致移動(dòng)有問題;
3)方法oTouchStart oTouchMove:獲取拖動(dòng)后圖片的半徑跟觸發(fā)時(shí)圖片的半徑的比值,再使用scale來實(shí)現(xiàn)縮放功能(items[index].disPtoO / items[index].r);獲取觸發(fā)時(shí)的圖片角度 拖動(dòng)后圖片的角度,再使用rotate來實(shí)現(xiàn)旋轉(zhuǎn)功能(items[index].angle = items[index].rotate);
4)方法imgEffects:調(diào)用濾鏡方法util.imgFliters(詳細(xì)可到https://jingyan.baidu.com/article/ed15cb1b9fd9bb1be3698183.html查看),根據(jù)設(shè)置的濾鏡值,進(jìn)行不同的濾鏡處理;而調(diào)用形狀方法util.imgShapes,根據(jù)設(shè)置的形狀值,進(jìn)行不同的切圖效果;
5)方法synthesis:用來把所有圖片的最終效果合成一個(gè)畫布,用于保存圖片到本地使用;
6)方法saveImg:把畫布保存到本地相冊。
let index = 0, // 當(dāng)前點(diǎn)擊圖片的index items = [], // 圖片數(shù)組信息 itemId = 1, // 圖片id,用于識別點(diǎn)擊圖片 fliter = 'init', // 默認(rèn)過濾類型(原圖) shape = 'init'; // 默認(rèn)形狀(原圖)const hCw = 1.62; // 圖片寬高比const canvasPre = 1; // 展示的canvas占mask的百分比const maskCanvas = wx.createCanvasContext('maskCanvas', this); // 創(chuàng)建 canvas 的繪圖上下文 CanvasContext 對象const util = require('../../utils/util.js');Page({ /** * 頁面的初始數(shù)據(jù) */ data: { itemList: [], showFliters: false, // 默認(rèn)不顯示過濾效果框 showShapes: false, // 默認(rèn)不顯示形狀效果框 fliters: [{ fliter: 'init', src: '../../images/init.jpg' }, { fliter: 'bw', src: '../../images/bw.jpg' }, { fliter: 'groundGlass', src: '../../images/groundGlass.jpg' }, { fliter: 'pictureStory', src: '../../images/pictureStory.jpg' }, { fliter: 'reminiscence', src: '../../images/reminiscence.jpg' }, { fliter: 'sketch', src: '../../images/sketch.jpg' }], shapes: [{ shape: 'circle', src: '../../images/init.jpg' }, { shape: 'star', src: '../../images/init.jpg' }, { shape: 'irregularityHeart', src: '../../images/init.jpg' }, { shape: 'SudokuHeart', src: '../../images/init.jpg' }] }, /** * 生命周期函數(shù)--監(jiān)聽頁面加載 */ onLoad: function(options) { items = this.data.itemList; wx.getSystemInfo({ // 獲取系統(tǒng)信息 success: sysData => { this.sysData = sysData // 設(shè)置畫布寬高,this.sysData.windowWidth為屏幕的寬度 this.setData({ canvasWidth: this.sysData.windowWidth * canvasPre, // 如果覺得不清晰的話,可以把所有組件、寬高放大一倍 canvasHeight: this.sysData.windowWidth * canvasPre * hCw, }) } }) }, // 上傳圖片 uploadImg() { let that = this; wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success (res) { // tempFilePath可以作為img標(biāo)簽的src屬性顯示圖片 that.setDropItem({ url: res.tempFilePaths[0] }); } }) }, // 設(shè)置圖片的信息 setDropItem(imgData) { let data = {}; // 存儲(chǔ)圖片信息 // 獲取圖片信息,網(wǎng)絡(luò)圖片需先配置download域名才能生效 wx.getImageInfo({ src: imgData.url, success: res => { // 初始化數(shù)據(jù) let maxWidth = 150, maxHeight = 150; // 設(shè)置最大寬高 if (res.width > maxWidth || res.height > maxHeight) { // 原圖寬或高大于最大值就執(zhí)行 if (res.width / res.height > maxWidth / maxHeight) { // 判斷比例使用最大值的寬或高作為基數(shù)計(jì)算 data.width = maxWidth; data.height = Math.round(maxWidth * (res.height / res.width)); } else { data.height = maxHeight; data.width = Math.round(maxHeight * (res.width / res.height)); } } data.image = imgData.url; // 顯示地址 data.initImage = imgData.url; // 原始地址 data.id = itemId; // id data.top = 0; // top定位 data.left = 0; // left定位 // 圓心坐標(biāo) data.x = data.left data.width / 2; data.y = data.top data.height / 2; data.scale = 1; // scale縮放 data.rotate = 1; // 旋轉(zhuǎn)角度 data.active = false; // 選中狀態(tài) items[items.length] = data; // 每增加一張圖片數(shù)據(jù)增加一條信息 this.setData({ itemList: items }) } }) }, // 點(diǎn)擊圖片 WraptouchStart: function(e) { // 循環(huán)圖片數(shù)組獲取點(diǎn)擊的圖片信息 for (let i = 0; i < items.length; i ) { items[i].active = false; if (e.currentTarget.dataset.id == items[i].id) { index = i; items[index].active = true; } } this.setData({ itemList: items }) // 獲取點(diǎn)擊的坐標(biāo)值 items[index].lx = e.touches[0].clientX; items[index].ly = e.touches[0].clientY; }, // 拖動(dòng)圖片 WraptouchMove(e) { items[index]._lx = e.touches[0].clientX; items[index]._ly = e.touches[0].clientY; items[index].left = items[index]._lx - items[index].lx; items[index].top = items[index]._ly - items[index].ly; items[index].x = items[index]._lx - items[index].lx; items[index].y = items[index]._ly - items[index].ly; items[index].lx = e.touches[0].clientX; items[index].ly = e.touches[0].clientY; this.setData({ itemList: items }) }, // 放開圖片 WraptouchEnd() { this.synthesis(); // 調(diào)用合成圖方法 }, // 點(diǎn)擊伸縮圖標(biāo) oTouchStart(e) { //找到點(diǎn)擊的那個(gè)圖片對象,并記錄 for (let i = 0; i < items.length; i ) { items[i].active = false; if (e.currentTarget.dataset.id == items[i].id) { index = i; items[index].active = true; } } //獲取作為移動(dòng)前角度的坐標(biāo) items[index].tx = e.touches[0].clientX; items[index].ty = e.touches[0].clientY; //移動(dòng)前的角度 items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty); //獲取圖片半徑 items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].left, items[index].top); }, oTouchMove: function(e) { //記錄移動(dòng)后的位置 items[index]._tx = e.touches[0].clientX; items[index]._ty = e.touches[0].clientY; //移動(dòng)的點(diǎn)到圓心的距離 items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx, items[index]._ty - 10) items[index].scale = items[index].disPtoO / items[index].r; //移動(dòng)后位置的角度 items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty) //角度差 items[index].new_rotate = items[index].angleNext - items[index].anglePre; //疊加的角度差 items[index].rotate = items[index].new_rotate; items[index].angle = items[index].rotate; //賦值 //用過移動(dòng)后的坐標(biāo)賦值為移動(dòng)前坐標(biāo) items[index].tx = e.touches[0].clientX; items[index].ty = e.touches[0].clientY; items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty) //賦值setData渲染 this.setData({ itemList: items }) }, // 計(jì)算坐標(biāo)點(diǎn)到圓心的距離 getDistancs(cx, cy, pointer_x, pointer_y) { var ox = pointer_x - cx; var oy = pointer_y - cy; return Math.sqrt( ox * ox oy * oy ); }, /* *參數(shù)cx和cy為圖片圓心坐標(biāo) *參數(shù)pointer_x和pointer_y為手點(diǎn)擊的坐標(biāo) *返回值為手點(diǎn)擊的坐標(biāo)到圓心的角度 */ countDeg: function(cx, cy, pointer_x, pointer_y) { var ox = pointer_x - cx; var oy = pointer_y - cy; var to = Math.abs(ox / oy); var angle = Math.atan(to) / (2 * Math.PI) * 360; if (ox < 0 && oy < 0) //相對在左上角,第四象限,js中坐標(biāo)系是從左上角開始的,這里的象限是正常坐標(biāo)系 { angle = -angle; } else if (ox <= 0 && oy >= 0) //左下角,3象限 { angle = -(180 - angle) } else if (ox > 0 && oy < 0) //右上角,1象限 { angle = angle; } else if (ox > 0 && oy > 0) //右下角,2象限 { angle = 180 - angle; } return angle; }, deleteItem: function(e) { let newList = []; for (let i = 0; i < items.length; i ) { if (e.currentTarget.dataset.id != items[i].id) { newList.push(items[i]) } } if (newList.length > 0) { newList[newList.length - 1].active = true; // 剩下圖片組最后一個(gè)選中 } items = newList; this.setData({ itemList: items }) }, // 打開遮罩層 openMask() { this.synthesis(); this.setData({ showCanvas: true }) }, synthesis() { // 合成圖片 maskCanvas.save(); maskCanvas.beginPath(); // 畫背景色(白色) maskCanvas.setFillStyle('#fff'); maskCanvas.fillRect(0, 0, this.data.canvasWidth, this.data.canvasHeight); items.forEach((currentValue, index) => { maskCanvas.save(); maskCanvas.translate(0, 0); maskCanvas.beginPath(); maskCanvas.translate(currentValue.x, currentValue.y); // 圓心坐標(biāo) maskCanvas.rotate(currentValue.angle * Math.PI / 180); maskCanvas.translate(-(currentValue.width * currentValue.scale / 2), -(currentValue.height * currentValue.scale / 2)) maskCanvas.drawImage(currentValue.image, 0, 0, currentValue.width * currentValue.scale, currentValue.height * currentValue.scale); maskCanvas.restore(); }) // reserve 參數(shù)為 false,則在本次調(diào)用繪制之前 native 層會(huì)先清空畫布再繼續(xù)繪制 maskCanvas.draw(false, (e) => { wx.canvasToTempFilePath({ canvasId: 'maskCanvas', success: res => { this.setData({ canvasTemImg: res.tempFilePath }) } }, this); }) }, // 點(diǎn)擊切換顯示過濾框 toggleFliters() { this.setData({ showFliters: !this.data.showFliters, showShapes: false }); }, // 點(diǎn)擊切換顯示形狀框 toggleShapes() { this.setData({ showShapes: !this.data.showShapes, showFliters: false }); }, // 圖片特效 imgEffects(e) { fliter = e.currentTarget.dataset.fliter || 'init'; shape = e.currentTarget.dataset.shape || 'init'; let that = this; items.forEach((currentValue, index) => { if (currentValue.active) { maskCanvas.save(); maskCanvas.beginPath(); util.imgShapes(maskCanvas, 0, 0, currentValue.width, currentValue.width, shape, 0, currentValue); // 圖片剪切不同形狀 maskCanvas.clearRect(0, 0, currentValue.width, currentValue.height); maskCanvas.drawImage(currentValue.initImage, 0, 0, currentValue.width, currentValue.height); maskCanvas.draw(false, function() { wx.canvasGetImageData({ // 獲取canvas區(qū)域的像素?cái)?shù)據(jù) canvasId: 'maskCanvas', x: 0, y: 0, width: currentValue.width, height: currentValue.height, success(res) { let imageData = res.data; util.imgFliters(maskCanvas, fliter, res); // 調(diào)用圖片濾鏡函數(shù) maskCanvas.clearRect(0, 0, currentValue.width, currentValue.height); // 清除舊的,不然會(huì)導(dǎo)致卡頓 maskCanvas.restore(); wx.canvasPutImageData({ // 將像素?cái)?shù)據(jù)繪制到canvas canvasId: 'maskCanvas', x: 0, y: 0, width: currentValue.width, height: currentValue.height, data: imageData, success(res) { wx.canvasToTempFilePath({ canvasId: 'maskCanvas', width: currentValue.width, height: currentValue.height, destWidth: currentValue.width, destHeight: currentValue.height, success: res => { items[index].image = res.tempFilePath that.setData({ itemList: items }) } }, this) } }) } }); }) }; }) }, // 關(guān)閉遮罩層 disappearCanvas() { this.setData({ showCanvas: false }) }, // 保存圖片到系統(tǒng)相冊 saveImg: function() { wx.saveImageToPhotosAlbum({ filePath: this.data.canvasTemImg, success: res => { wx.showToast({ title: '保存成功', icon: "success" }) }, fail: res => { wx.openSetting({ success: settingdata => { if (settingdata.authSetting['scope.writePhotosAlbum']) { console.log('獲取權(quán)限成功,給出再次點(diǎn)擊圖片保存到相冊的提示。') } else { console.log('獲取權(quán)限失敗,給出不給權(quán)限就無法正常使用的提示') } }, fail: error => { console.log(error) } }) wx.showModal({ title: '提示', content: '保存失敗,請確保相冊權(quán)限已打開', }) } }) }})
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。