您好,登錄后才能下訂單哦!
先看下微信小程序canvas拖拽功能
組件地址
github.com/jasondu/wx-… readme近期補上
實現效果
如何實現
由于movable-view無法實現旋轉,所以選擇使用canvas
需要解決的問題
看起來挺簡單的嘛,就把上面這幾個問題解決了,就可以實現功能了;接下來我們一一解決。
如何將多個元素渲染到canvas上
定義一個DragGraph類,傳入元素的各種屬性(坐標、尺寸…)實例化后推入一個 渲染數組 里,然后再循環這個數組調用實例中的渲染方法,這樣就可以把多個元素渲染到canvas上了。
如何知道手指在元素上、如果多個元素重疊如何知道哪個元素在最上層
在DragGraph類中定義了判斷點擊位置的方法,我們在canvas上綁定touchstart事件,將手指的坐標傳入上面的方法,我們就可以知道手指是點擊到元素本身,還是刪除圖標或者變換大小的圖標上了,這個方法具體怎么判斷后面會講解。
通過循環 渲染數組 判斷是非點擊到哪個元素到,如果點擊中了多個元素,也就是多個元素重疊,那第一個元素就是最上層的元素啦。
###如何實現拖拽元素
通過上面我們可以判斷手指是否在元素上,當touchstart事件觸發時我們記錄當前的手指坐標,當touchmove事件觸發時,我們也知道這時的坐標,兩個坐標取差值,就可以得出元素位移的距離啦,修改這個元素實例的x和y,再重新循環渲染 渲染數組 就可以實現拖拽的功能。
如何縮放、旋轉、刪除元素
這一步相對比較難一點,我會通過示意圖跟大家講解。
我們先講縮放和旋轉
通過touchstart和touchmove我們可以獲得旋轉前的旋轉后的坐標,圖中的線A為元素的中點和旋轉前點的連線;線B為元素中點和旋轉后點的連線;我們只需要求A和B兩條線的夾角就可以知道元素旋轉的角度。縮放尺寸為A和B兩條線長度之差。
計算旋轉角度的代碼如下:
const centerX = (this.x + this.w) / 2; // 中點坐標 const centerY = (this.y + this.h) / 2; // 中點坐標 const diffXBefore = px - centerX; // 旋轉前坐標 const diffYBefore = py - centerY; // 旋轉前坐標 const diffXAfter = x - centerX; // 旋轉后坐標 const diffYAfter = y - centerY; // 旋轉后坐標 const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180; const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180; // 旋轉的角度 this.rotate = currentGraph.rotate + angleAfter - angleBefore;
計算縮放尺寸的代碼如下:
// 放大 或 縮小 this.x = currentGraph.x - (x - px); this.y = currentGraph.y - (x - px);
下面介紹下小程序canvas截圖組件
最近做一個小程序的過程中,需要用到截圖功能,網上搜了一下,發現沒有符合要求的,就自己搞了個組件,方便復用。
目前功能很簡單,傳入寬高和圖片路徑即可,寬高是為了計算截圖的比例,只支持縮放和移動。
實現思路是:
1.模擬一個截取框;
2.移動圖片位置,縮放圖片;
3.獲取圖片在其中的位置(left,top,width,height);
4.使用canvas繪制圖片,然后截取就ok了。
其中第二步的縮放圖片比較麻煩,縮放中心點以及平滑縮放
以下是我的實現方式
wxml:
<!--component/picPro/picPro.wxml--> <scroll-view class='body' hidden="{{hidden}}"> <view class='flex-column flex-between full-height full-width' bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend"> <view class='bg_dark out_item'></view> <view class='flex-row main flex-between' style='height:{{(windowWidth - margin.left - margin.right)/ratio + "px"}}'> <view class='bg_dark main_item full-height' style='width:{{margin.left + "px"}}'></view> <view class='inner relative full-width' id='showArea'> <image class='absolute img' src='{{src}}' ></image> <canvas canvas-id='imgCanvas' class='absolute img_canvas full-height full-width' /> <view class='absolute inner_item left_top'></view> <view class='absolute inner_item right_top'></view> <view class='absolute inner_item right_bottom'></view> <view class='absolute inner_item left_bottom'></view> </view> <view class='bg_dark main_item full-height' style='width:{{margin.right + "px"}}'></view> </view> <view class='bg_dark out_item flex-column flex-end'> <view class='flex-around text_white text_bg'> <view catchtap='outputImg' data-type='1'><text>重新上傳</text></view> <view catchtap='getImg'><text>選擇圖片</text></view> </view> </view> <!-- --> <view class='absolute full-width full-height bg_black'></view> </view> </scroll-view>
wxss:(其中引入了一個公共樣式,關于flex布局的,看樣式名也能猜到)
/* component/picPro/picPro.wxss */ @import '../../resource/style/flex.wxss'; .body{ position: fixed; top: 0; right: 0; bottom: 0; left: 0; } .text_white{ color: white; } .main{ } .out_item{ width: 100%; height: 100%; flex: 1; } .bg_dark{ background-color: rgba(0, 0, 0, 0.85) } .main_item{ width: 15px; } .inner{ outline: 3rpx solid white; background-color: rgba(0, 0, 0, 0.12); box-shadow: 0 0 4px rgba(0, 0, 0, 0.5) inset; } .inner_item{ width: 8px; height: 8px; } .inner_item.left_top{ border-left: 3px solid white; border-top: 3px solid white; left: -3px; top: -3px; } .inner_item.right_top{ border-right: 3px solid white; border-top: 3px solid white; right: -3px; top: -3px; } .inner_item.right_bottom{ border-right: 3px solid white; border-bottom: 3px solid white; right: -3px; bottom: -3px; } .inner_item.left_bottom{ border-left: 3px solid white; border-bottom: 3px solid white; left: -3px; bottom: -3px; } .img{ z-index: -1; } .bg_black{ background-color:black; z-index: -2; } .text_bg{ padding-bottom: 2em; font-size: 0.9em; } .img_canvas{ opacity: 0.5; } .newImg{ z-index: 2 }
js:
// component/picPro/picPro.js const state = { // 可用區域body window: { width: 0, height: 0 }, // 原始圖片信息 originImg: { width: 0, height: 0 }, // 第一次圖片縮放信息 firstScaleImg: { width: 0, height: 0 }, // 截取區域信息 interArea: { width: 0, height: 0 }, // 單手觸摸位置 touchLast: { x: 0, y: 0 }, // 滑動距離 touchMove: { x: 0, y: 0 }, // 滑動離開時圖片狀態 moveImgState: { width: 0, height: 0, top: 0, left: 0, }, // 雙手觸摸位置 touchList: [{ x: 0, y: 0 }, { x: 0, y: 0 }], // 圖片縮放比例 scale: 1, } Component({ /** * 組件的屬性列表 */ properties: { //寬(非實際值) width: { type: Number, value: 600 }, //高 height: { type: Number, value: 300 }, //圖片路徑 src: { type: String, value: "" }, //顯示隱藏 hidden: { type: Boolean, value: false }, //截取框的信息 margin: { type: Object, value: { left: 15, right: 15, top: 200, bottom: 200, } } }, ready() { this.initialize(); // const canvas = wx.createCanvasContext('imgCanvas', this); // canvas.draw(false, () => { console.log('ccc') }, this); }, /** * 組件的初始數據 */ data: { touchRange: 8, img: { width: 0, height: 0, top: 0, left: 0, }, canvas: {}, ratio: 0, originImg: { width: 0, height: 0 } }, /** * 組件的方法列表 */ methods: { touchstart(e) { // console.log("touchstart", e); }, touchmove(e) { if (e.touches.length === 1) { this.singleSlip(e.touches[0]) } else { this.doubleSlip(e.touches) } }, touchend(e) { // console.log("touchend", e); const x = 0, y = 0; state.touchLast = { x, y }; state.touchMove = { x, y }; state.touchList = [{ x, y }, { x, y }]; state.moveImgState = this.data.img; // console.log(this.data.img); }, // 單手滑動操作 singleSlip(e) { const { clientX: x, clientY: y } = e; const that = this; if (state.touchLast.x && state.touchLast.y) { state.touchMove = { x: x - state.touchLast.x, y: y - state.touchLast.y }; state.touchLast = { x, y }; const move = (_x = false, _y = false) => { const bottom = that.data.img.height + that.data.img.top; const right = that.data.img.width + that.data.img.left; const h = state.interArea.height; const w = state.interArea.width; const param = {}; if (_x) { if (right > w && that.data.img.left < 0) { param.left = that.data.img.left + state.touchMove.x * 0.1 } else if (right <= w && state.touchMove.x > 0) { param.left = that.data.img.left + state.touchMove.x * 0.1 } else if (that.data.img.left >= 0 && state.touchMove.x < 0) { param.left = that.data.img.left + state.touchMove.x * 0.1 } }; if (_y) { if (bottom > h && that.data.img.top < 0) { param.top = that.data.img.top + state.touchMove.y * 0.1 } else if (bottom <= h && state.touchMove.y > 0) { param.top = that.data.img.top + state.touchMove.y * 0.1 } else if (that.data.img.top >= 0 && state.touchMove.y < 0) { param.top = that.data.img.top + state.touchMove.y * 0.1 } }; // console.log(param); that.setImgPos(param) }; if (state.scale == 1) { if (that.data.img.width == state.interArea.width) { move(false, true) } else { move(true, false) } } else { move(true, true) } } else { state.touchLast = { x, y } } }, // 雙手縮放操作 doubleSlip(e) { const that = this; const { clientX: x0, clientY: y0 } = e[0]; const { clientX: x1, clientY: y1 } = e[1]; if (state.touchList[0].x && state.touchList[0].y) { let changeScale = (Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) - Math.sqrt((state.touchList[1].x - state.touchList[0].x) * (state.touchList[1].x - state.touchList[0].x) + (state.touchList[1].y - state.touchList[0].y) * (state.touchList[1].y - state.touchList[0].y))) * 0.0005; changeScale = changeScale >= 1.5 ? 1.5 : (changeScale <= -1 ? -1 : changeScale); state.scale = that.data.img.width / state.firstScaleImg.width < 1 ? 1 : (state.scale > 2.5 ? 2.5 : 1 + changeScale); let width = state.firstScaleImg.width * (state.scale - 1) + state.moveImgState.width; width = width < state.firstScaleImg.width ? state.firstScaleImg.width : width; let height = state.firstScaleImg.height * (state.scale - 1) + state.moveImgState.height; height = height < state.firstScaleImg.height ? state.firstScaleImg.height : height; let left = width * (1 - state.scale) / 4 + state.moveImgState.left; left = left * (-1) > width - state.interArea.width ? state.interArea.width - width: left > 0 ? 0 : left; let top = height * (1 - state.scale) / 4 + state.moveImgState.top; top = top * (-1) > height - state.interArea.height ?state.interArea.height - height : top > 0 ? 0 : top; const setImgObj = { width, height, left, top }; that.setImgPos(setImgObj) } else { state.touchList = [{ x: x0, y: y0 }, { x: x1, y: y1 }] } }, // 獲取可用區域寬高 getScreenInfo() { const that = this; return new Promise((resolve, reject) => { wx.getSystemInfo({ success: function (res) { const { windowHeight, windowWidth } = res; state.window = { windowHeight, windowWidth }; that.setData({ windowHeight, windowWidth }) // console.log(state.window); resolve(res); }, }) }) }, setShowArea() { const that = this; const w = state.window.windowWidth - that.data.margin.left - that.data.margin.right; const h = (that.data.height / that.data.width) * w; }, outputImg() { this.setData({ hidden: true, }) }, getImgInfo(path) { return new Promise((resolve, reject) => { wx.getImageInfo({ src: path, success(res) { console.log(res); resolve(res); }, fail(err) { reject(err) } }) }) }, // 設置圖片 setImgPos({ width, height, top, left }) { width = width || this.data.img.width; height = height || this.data.img.height; top = top || this.data.img.top; left = left || this.data.img.left this.setData({ img: { width, height, top, left } }) }, // 初始化圖片位置大小 initialize() { const that = this; const ratio = that.data.width / that.data.height; this.getScreenInfo().then(res => { console.log(res); state.interArea = { width: res.windowWidth - that.data.margin.left - that.data.margin.right + 2, height: (res.windowWidth - that.data.margin.left - that.data.margin.right) / ratio }; console.log("interArea", state.interArea) that.getImgInfo(that.data.src).then(imgInfo => { const { width, height } = imgInfo; const imgRatio = width / height; state.originImg = { width, height }; that.setData({ ratio: ratio }); if (imgRatio > ratio) { that.setImgPos({ height: state.interArea.height, width: state.interArea.height * imgRatio }) } else { that.setImgPos({ height: state.interArea.width / imgRatio, width: state.interArea.width, }) }; state.firstScaleImg = { width: that.data.img.width, height: that.data.img.height } }); }); }, // 截圖 getImg(){ const that = this; // console.log('dudu', that.data.img); const canvas = wx.createCanvasContext('imgCanvas', this); const {width,height,left,top} = that.data.img; const saveImg = ()=>{ console.log('開始截取圖片'); wx.canvasToTempFilePath({ canvasId:"imgCanvas", success(res){ // console.log(res); that.setData({ hidden:true, // src:"" }); that.triggerEvent("putimg", { imgUrl: res.tempFilePath},{}); }, fail(err){ console.log(err) } },that) }; canvas.drawImage(that.data.src, left, top, width, height); canvas.draw(false, () => { saveImg() }, that) } } })
引用的時候除了寬高路徑以外,需要wx:if;如果不卸載組件,會出現只能截一次的bug
因為小程序里面沒有類似vue中catch的觀測數據變化的東西,也不想為了個組件專門去搞一個,就用這種方式代替了,嘻嘻,好敷衍。。
總結
以上所述是小編給大家介紹的微信小程序canvas拖拽、截圖組件功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。