<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          小程序canvas寫一個(gè)簡單的圖片應(yīng)用

          共 8015字,需瀏覽 17分鐘

           ·

          2020-12-18 18:29

          作者:JesseLuo
          來源:SegmentFault 思否社區(qū)




          很早就想寫一個(gè)移動端圖片小應(yīng)用了,最近剛好休業(yè)在家,于是抽出時(shí)間實(shí)現(xiàn)下自己的小想法。




          應(yīng)用展示


          截圖



          在線預(yù)覽





          需求


          既然是小應(yīng)用,那就希望最終成品是有?適用的場景?且是?有價(jià)值?的


          需求來源


          在以前工作生活中,經(jīng)常會無意中獲得同事的?美照,這時(shí)我們想要把這張照片做成表情包,一般給圖片添加幾個(gè)說明文字,一個(gè)有意思的溝通工具(表情包)就完成了。


          需求分析


          基于以上需求的拆解


          可以將要應(yīng)用功能實(shí)現(xiàn)整理一下


          • 用戶需要上傳一張圖片
          • 可以添加文字
          • 文字可以作?樣式調(diào)整?和?旋轉(zhuǎn)縮放
          • 另外我們希望還可以插入一些貼圖
          • 貼圖可以做?旋轉(zhuǎn)縮放
          • 用戶導(dǎo)出圖片到相冊




          實(shí)現(xiàn)


          github倉庫?https://github.com/luosijie/face-maker

          這個(gè)應(yīng)用是用小程序開發(fā)的

          • 使用框架:mpx
          • 使用技術(shù):小程序canvas

          狀態(tài)管理


          import?{?createStore?}?from?'@mpxjs/core'

          const?store?=?createStore({
          ??state:?{
          ????cavas:?null,?????????//?cnavas實(shí)例
          ????ctx:?null,???????????//?canvas上下文實(shí)例
          ????elements:?[],????????//?canvas元素
          ????activeIndex:?null,???//?當(dāng)前編輯中的元素索引
          ????mode:?'background', ?//?當(dāng)前編輯模式:background, text, sticker
          ????fontStyle:?{?????????//?文字默認(rèn)樣式
          ??????opacity:?1,
          ??????fillStyle:?'#000000',
          ??????strokeStyle:?'#000000'
          ????}
          ??},
          ??mutations:?{
          ????setCanvas?(state,?data)?{
          ??????state.canvas?=?data
          ????},
          ????setCtx?(state,?data)?{
          ??????state.ctx?=?data
          ????},
          ????setElements?(state,?data)?{
          ??????state.elements?=?data
          ????},
          ????setMode?(state,?data)?{
          ??????state.mode?=?data
          ????},
          ????setActiveIndex?(state,?data)?{
          ??????state.activeIndex?=?data
          ????},
          ????setFontStyle?(state,?{?key,?data?})?{
          ??????state.fontStyle[key]?=?data
          ????},
          ????//?添加文字
          ????addText?(state)?{
          ??????const?size?=?50
          ??????const?string?=?'請輸入文字'
          ??????const?text?=?{
          ????????type:?'text',
          ????????data:?string,
          ????????scale:?1,
          ????????size,
          ????????left:?100,
          ????????top:?100,
          ????????rotate:?0,
          ????????opacity:?state.fontStyle.opacity,
          ????????fillStyle:?state.fontStyle.fillStyle,
          ????????strokeStyle:?state.fontStyle.strokeStyle
          ??????}
          ??????state.elements.push(text)
          ??????state.activeIndex?=?state.elements.length?-?1
          ????},
          ????//?添加貼圖
          ????addSticker?(state,?data)?{
          ??????state.elements.push(data)
          ??????state.activeIndex?=?state.elements.length?-?1
          ????},
          ????//?刪除當(dāng)前選中
          ????deleteActiveELement?(state)?{
          ??????state.elements.splice(state.activeIndex,?1)
          ??????state.activeIndex?=?null
          ????},
          ????//?清空畫布
          ????clear?(state)?{
          ??????state.elements?=?[]
          ??????state.activeIndex?=?null
          ????}
          ??}
          })

          export?default?store

          畫布初始化


          //?初始化畫布
          async?initCanvas()?{
          ??const?query?=?this.createSelectorQuery()
          ??query
          ????.select('#canvas')
          ????.fields({?node:?true,?size:?true?})
          ????.exec(async?res?=>?{
          ??????const?canvas?=?res[0].node
          ??????const?ctx?=?canvas.getContext('2d')
          ??????store.commit('setCanvas',?canvas)
          ??????store.commit('setCtx',?ctx)

          ??????await?this.loadImage('/images/icon-rotate.png').then(res?=>?{
          ????????this.image.rotate?=?res
          ??????})

          ??????canvas.width?=?res[0].width?*?this.dpr
          ??????canvas.height?=?res[0].height?*?this.dpr
          ??????ctx.scale(this.dpr,?this.dpr)
          ??????this.drawGrid()
          ????})
          }

          繪制圖片


          /**
          ?*?繪制圖片
          ?*?@param?{?Object?}?ele?canvas元素
          ?*/
          drawImage(ele)?{
          ??this.ctx.save()
          ??const?width?=?ele.width
          ??const?height?=?ele.height
          ??const?centerX?=?ele.left?+?ele.width?/?2
          ??const?centerY?=?ele.top?+?ele.height?/?2
          ??this.ctx.translate(centerX,?centerY)
          ??this.ctx.rotate(ele.rotate)
          ??this.ctx.drawImage(ele.data,?ele.left?-?centerX,?ele.top?-?centerY,?width,?height)
          ??this.ctx.restore()
          }

          繪制文字


          /**
          ?*?繪制文字
          ?*?@param?{?Object?}?ele?canvas元素
          ?*/
          drawText(ele)?{
          ??this.ctx.save()
          ??const?width?=?ele.size?*?ele.data.length
          ??const?height?=?ele.size
          ??const?centerX?=?ele.left?+?width?/?2
          ??const?centerY?=?ele.top?+?height?/?2
          ??this.ctx.translate(centerX,?centerY)
          ??this.ctx.rotate(ele.rotate)
          ??this.ctx.font?=?`${ele.size}px?bold?sans-serif`
          ??this.ctx.globalAlpha?=?ele.opacity
          ??this.ctx.fillStyle?=?ele.fillStyle
          ??this.ctx.strokeStyle?=?ele.strokeStyle
          ??//?this.ctx.lineWidth?=?2
          ??this.ctx.textBaseline?=?'top'
          ??console.log('draw-text',?ele)
          ??this.ctx.fillText(ele.data,?ele.left?-?centerX,?ele.top?-?centerY)
          ??this.ctx.strokeText(ele.data,?ele.left?-?centerX,?ele.top?-?centerY)
          ??this.ctx.restore()
          }

          繪制控制元件


          initController(ele)?{
          ??const?cs?=?this.convert2ControllerSize(ele)
          ??this.ctx.save()
          ??this.ctx.strokeStyle?=?'#eee'
          ??this.ctx.translate(cs.centerX,?cs.centerY)
          ??this.ctx.rotate(cs.rotate)
          ??//?繪制虛線邊框
          ??this.ctx.setLineDash([10,?5],?5)
          ??this.ctx.strokeRect(cs.left?-?cs.centerX,?cs.top?-?cs.centerY,?cs.width,?cs.height)
          ??//?繪制控制點(diǎn)-旋轉(zhuǎn)
          ??this.ctx.drawImage(this.image.rotate,?cs.left?+?cs.width?-?10?-?cs.centerX,?cs.top?+?cs.height?-?10?-?cs.centerY,?20,?20)
          ??this.ctx.restore()
          }

          畫布渲染函數(shù)


          //?畫布渲染函數(shù)
          renderCanvas()?{
          ??this.ctx.clearRect(0,?0,?this.ctx.canvas.width,?this.ctx.canvas.height)
          ??this.drawGrid()
          ??console.log('draw-background',?this.background)
          ??if?(this.background)?this.drawImage(this.background)
          ??for?(let?i?=?0;?i?????const?ele?=?this.elements[i]
          ????//?渲染背景
          ????if?(ele.type?===?'background')?{
          ??????this.drawImage(ele)
          ????}
          ????if?(ele.type?===?'sticker')?{
          ??????this.drawImage(ele)
          ????}
          ????//?渲染文字
          ????if?(ele.type?===?'text')?{
          ??????this.drawText(ele)
          ????}
          ????//?選中元素添加控制元件
          ????if?(this.activeIndex?===?i)?{
          ??????this.initController(ele)
          ????}
          ??}
          }



          事件監(jiān)聽


          移動


          //?移動事件綁定函數(shù)
          handleMove(e)?{
          ??console.log('mouse-move',?e)
          ??if?(e.touches.length?>?1)?return
          ??const?x?=?e.touches[0].x
          ??const?y?=?e.touches[0].y
          ??const?dx?=?this.startTouches[0].x?-?x
          ??const?dy?=?this.startTouches[0].y?-?y
          ??const?elements?=?this.elements.slice()
          ??elements[this.activeIndex?||?0].left?=?this.startSelected.left?-?dx
          ??elements[this.activeIndex?||?0].top?=?this.startSelected.top?-?dy
          ??store.commit('setElements',?elements)
          }

          旋轉(zhuǎn)


          //?旋轉(zhuǎn)綁定函數(shù)
          handleRotate(e)?{
          ??console.log('handleRotate')
          ??const?start?=?this.startTouches[0]
          ??const?end?=?e.touches[0]
          ??const?center?=?{
          ????x:?this.startSelected.centerX,
          ????y:?this.startSelected.centerY
          ??}
          ??const?startLength?=?Math.sqrt((center.x?-?start.x)?**?2?+?(center.y?-?start.y)?**?2)
          ??const?endLength?=?Math.sqrt((center.x?-?end.x)?**?2?+?(center.y?-?end.y)?**?2)
          ??const?radian?=?this.convert2Radian(start,?end,?center)
          ??const?scale?=?endLength?/?startLength
          ??const?elements?=?this.elements.slice()
          ??const?selected?=?elements[this.activeIndex]
          ??//?旋轉(zhuǎn)
          ??selected.rotate?=?this.startSelected.rotate?-?radian
          ??//?縮放
          ??if?(selected.type?===?'text')?{
          ????selected.left?=?this.startSelected.centerX?-?this.startSelected.size?*?this.startSelected.data.length?*?scale?/?2
          ????selected.top?=?this.startSelected.centerY?-?this.startSelected.size?*?scale?/?2
          ????selected.size?=?this.startSelected.size?*?scale
          ??}
          ??if?(selected.type?===?'sticker')?{
          ????selected.left?=?this.startSelected.centerX?-?this.startSelected.width?*?scale?/?2
          ????selected.top?=?this.startSelected.centerY?-?this.startSelected.height?*?scale?/?2
          ????selected.width?=?this.startSelected.width?*?scale
          ????selected.height?=?this.startSelected.height?*?scale
          ??}
          ??store.commit('setElements',?elements)
          }

          縮放


          //?縮放事件綁定函數(shù)
          handleScale(e)?{
          ??if?(e.touches.length?!==?2?||?this.mode?!==?'background')?return
          ??const?startLength?=?Math.sqrt(
          ????(this.startTouches[0].x?-?this.startTouches[1].x)?**?2?+
          ??????(this.startTouches[0].y?-?this.startTouches[1].y)?**?2
          ??)
          ??const?endLength?=?Math.sqrt(
          ????(e.touches[0].x?-?e.touches[1].x)?**?2?+?(e.touches[0].y?-?e.touches[1].y)?**?2
          ??)
          ??const?scale?=?endLength?/?startLength
          ??const?elements?=?this.elements.slice()
          ??const?selected?=?elements[this.activeIndex?||?0]
          ??selected.left?=?this.startSelected.centerX?-?this.startSelected.width?*?scale?/?2
          ??selected.top?=?this.startSelected.centerY?-?this.startSelected.height?*?scale?/?2
          ??selected.width?=?this.startSelected.width?*?scale
          ??selected.height?=?this.startSelected.height?*?scale
          ??//?elements[this.activeIndex?||?0].scale?=?this.startSelected.scale?*?scale
          ??store.commit('setElements',?elements)
          }

          導(dǎo)出圖片


          export()?{
          ??if?(!store.state.elements.length)?{
          ????wx.showToast({
          ??????title:?'加點(diǎn)東西再導(dǎo)出吧',
          ??????icon:?'none'
          ????})
          ????return
          ??}
          ??wx.showModal({
          ????title:?'提示',
          ????content:?'圖片將保存到手機(jī)相冊',
          ????success(res)?{
          ??????if?(res.confirm)?{
          ????????console.log('export-canvas',?store.state.ctx)
          ????????const?canvas?=?store.state.canvas
          ????????wx.canvasToTempFilePath({
          ??????????x:?0,
          ??????????y:?0,
          ??????????width:?canvas.width,
          ??????????height:?canvas.height,
          ??????????canvas,
          ??????????complete(res)?{
          ????????????if?(res.errMsg?===?'canvasToTempFilePath:ok')?{
          ??????????????wx.saveImageToPhotosAlbum({
          ????????????????filePath:?res.tempFilePath,
          ????????????????success(res)?{
          ??????????????????wx.showToast({
          ????????????????????title:?'圖片保存成功',
          ????????????????????icon:?'none'
          ??????????????????})
          ????????????????}
          ??????????????})
          ????????????}
          ??????????}
          ????????})
          ??????}
          ????}
          ??})
          }

          項(xiàng)目持續(xù)更新優(yōu)化,感興趣的朋友可以關(guān)注一下,一起交流學(xué)習(xí)
          github倉庫?https://github.com/luosijie/face-maker



          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。

          -?END -


          瀏覽 22
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.豆花豆花视频网站 | 在线播放内射 | 黄色片免费的 | 3344在线观看免费下载视频 | 国产三级精品视频 |