最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • canvas : 搞个涂鸦板玩玩吧

    正文概述 掘金(三里清风)   2021-04-01   615

    前言

    最近接了一个项目,里面有一个涂鸦板的模块,刚刚好最近一直在面试,就顺便拿这个项目来进行知识点复习

    明确功能

    先看一下成品图。这里因为主题的问题下面被截断了一点,但是原来的主题实在不好看,所以就将就一下吧。

    canvas : 搞个涂鸦板玩玩吧

    那从上图我们大概可以知道有下面这些功能需要实现

    • 支持涂鸦
    • 支持修改画笔颜色
    • 支持修改画笔大小
    • 支持上一步
    • 支持下一步
    • 支持添加背景图片
    • 支持生成图片

    初始化

    上面已经明确了我们需要完成一个什么样的涂鸦板了,那现在就先来初始化一个模版吧

    <template>
      <div class="container">
        
          <!--  画板-->
          <div class="canvas-container">
            <h3>画板</h3>
            <canvas
                  :width="760"
                  :height="610"
                  ref="myPalette"
                  class="palette"
                  @mousedown="handleDownCanvas"
                  @mouseup="handleOverMove"
                  @mousemove="handleMove"
                  @mouseout="handleOverMove"
              />
              <img style="margin-left: 30px" :src="image" >
          </div>
    
          <div>
            鼠标坐标x: {{movex}}y:{{movey}}
          </div>
    
          <div class="container-item">
            <button class="button-item" @click="handlePre">上一步</button>
            <button class="button-item" @click="handleNext">下一步</button>
            <button class="button-item" @click="handleSetImg">选择图片</button>
            <button class="button-item" @click="createImage">生成图片</button>
          </div>
    
          <div class="container-item">
            <h4>画笔颜色</h4>
            <span
                class="color-item"
                v-for="(item,index) in colors"
                :style="{'background':item}"
                @click="handleSetColor(item)"
                :key="index"
            />
          </div>
    
          <div class="container-item">
            <h4>画笔大小</h4>
            <div class="size-item" v-for="(item,index) in size" :key="index" @click="handleSetSize(item.size)">{{item.name}}</div>
          </div>
    
      </div>
    </template>
    import mixin from "./mixin"
    export default {
      name: "palette",
      mixins:[mixin],
      data(){
        return{
          // 画笔颜色
          colors:[
            '#f1d506','#0924de','#08e31e','#f32f15','#cccccc','#5ab639'
          ],
          size:[
            {name:"小",size:1},
            {name:"中",size:2},
            {name:"大",size:3}
          ],
          // canvas对象
          context: {},
          // 保存绘画的路径
          lines:[],
          // 是否开始绘制
          canvasMoveUse: false,
          // 画笔的设置
          config:{
            lineWidth:1,              //  线条的宽度
            shadowBlur:1,             //  阴影模糊的程度
            shadowColor:"#f1d506",    //  阴影的颜色
            strokeStyle:"#f10649"     //  笔触的颜色
          },
          preHandle:[],   // 上一步
          nextHandle:[],   // 下一步
          movex:0,
          movey:0,
          image:null
        }
      },
    }
    

    一顿操作之后,页面展示应该如图:

    canvas : 搞个涂鸦板玩玩吧 当然现在的控制台应该很多报错,因为我们还没有将对应的函数等添加到方法中,接下来就开始完善各种功能,在开始之前,先将canvas添加到data中,方法我们之后进行调用

    export default {
        ...
        mounted() {
            this.init()
        },
        methods:{
            init(){
               const canvas = this.$refs.myPalette
               this.context = canvas.getContext("2d")
            }
        }
    }
    

    开发功能模块

    涂鸦功能实现

    因为接下来的功能都是得在能涂鸦的情况下实现,所以最开始就得先实现这个最基础的功能啦。
    在开始之前,首先得明确一下,canvas是如何做这个绘画的功能的呢?我们知道,当我们开始画图的时候,通常是从某一点到另外一点的线条,那也就表明了,其实我们做的涂鸦功能,也是从某一点(x,y)到另外一点(x,u)路径的绘制,知道了这个之后,就可以开始我们的操作了。

    看一下初始化的代码,我们已经给canvas添加了mousedownmouseupmousemovemouseout,它们分别对应鼠标的按下,抬起,移动,移出元素,那我们就根据四个事件来完善涂鸦的功能。

    鼠标按下时

    知道了绘制是从一点到另外一点的路径之后,那当我们开始绘制的时候,需要一个起点,而这个起点其实就是鼠标按下时候的坐标点,那就得先拿到鼠标的坐标点啦,先看一下代码吧。

    // 在canvas中按下鼠标
    handleDownCanvas(e){
        // 是否可以开始移动绘制
        this.canvasMoveUse = true
        // 获取当前鼠标按下的位置
        const {canvasX,canvasY} = this.getEventXY(e)
        // 重置画笔配置
        this.handleSetConfig()
        // 清除子路径
        this.context.beginPath()
        // 记录起点
        this.context.moveTo(canvasX, canvasY)
        // 参数的值 x y width height
        const pre = this.context.getImageData(0, 0, 700, 600)
        // 记录当前操作,便于后续的撤销操作
        this.preHandle.push(pre)
        // 重新绘画之后清除所有下一步
        this.nextHandle = []
    },
    

    然后逐步来说明一下每个模块代码的作用
    canvasMoveUse

    • 这个变量主要的作用就是用来决定是否要开始绘制路径

    getEventXY()

    • 获取鼠标按下或者移动的时候的坐标点

    在这里得先来了解一下最基本的获取坐标的知识。 看一下点击或者移动的时候,获取到的当前对象

    canvas : 搞个涂鸦板玩玩吧 在这个里面我们需要先了解一下几个属性值表示的意思

    • clienX/Y: 当鼠标事件发生时,鼠标相对于浏览器的X或Y轴距离
    • offsetX/Y:当鼠标事件发生时,鼠标相对于事件源X或Y轴的位置
    • screenX/Y:当鼠标事件发生时,鼠标相对于显示器屏幕X或Y轴的位置

    用图示就是

    canvas : 搞个涂鸦板玩玩吧 还有一点就是,在PC端获取坐标点跟在手机端获取的方式有些差异,但是目前这个涂鸦板只考虑PC,所以手机端就暂时不说,有兴趣可以百度一下
    了解完这些之后再来看一下获取鼠标坐标点的函数,就会清晰很多了

    getEventXY(e){
        // 默认获取pc
        let canvasX = e.offsetX
        let canvasY = e.offsetY
        this.movex =  canvasX
        this.movey =  canvasY
        // 使用手机的时候
        if(!this.isPC()){
            canvasX = e.changedTouches[0].offsetX
            canvasY = e.changedTouches[0].offsetY
        }
        return {canvasX,canvasY}
    },
    

    完成之后,在点击移动之后,下面的鼠标坐标也会出现相应的坐标点。
    handleSetConfig

    • 设置画笔,设置为config中的参数,而config的颜色默认的设置为颜色阴影数组中的第一个

    beginPath

    • 清除绘画的路径,如果不添加这个参数,每次按下进行绘制的时候,都会被认为是在同一条路径上进行绘制,那这样的话就会导致路线全部连在一起,所有的颜色都会变成你最后选择的颜色

    moveTo

    • 设置绘制开始的起点

    getImageData

    • 生成当前的canvas的图像,记录下来,方便后面进行上一步的操作

    鼠标抬起,移出

    这两个就没什么特别好说的了,主要就是因为抬起移出的时候,如果不清除掉移动,那就会导致还可以继续进行绘制

    // 结束绘画
    handleOverMove(){
      this.canvasMoveUse = false
    },
    

    鼠标移动时

    // 移动
    handleMove(e){
      if (!this.canvasMoveUse) return
      // 获取坐标点
      const {canvasX,canvasY} = this.getEventXY(e)
      // 链接每个点
      this.context.lineTo( canvasX ,canvasY)
      //绘制已定义的路径
      this.context.stroke()
    }
    

    这个最主要就是连接点跟点,绘制成线,其他的都是canvas的内容,具体的api调用直接上文档吧 canvas
    到这里最基础的涂鸦功能就完成,现在尝试一下绘制,不出意外就没问题啦

    支持修改画笔颜色,大小

    之前已经有config这个配置参数了跟handleSetConfig这个设置画笔的配置函数了,那修改大小跟颜色其实就是修改config的参数,然后调用一下handleSetConfig就行了。

    // 设置画笔的颜色
    handleSetColor(color){
      this.config.shadowColor = color  // 阴影
      this.config.strokeStyle = color  // 画笔颜色
      this.handleSetConfig()
    },
    
    // 设置画笔大小
    handleSetSize(size){
      this.config.lineWidth = size
      this.handleSetConfig()
    },
    

    支持上一步,下一步

    上一步的功能,其实就是把当前画布上的内容重置为上一次画布上的内容,在完善涂鸦功能的时候已经把当前的画布内容保存下来了。

    handleDownCanvas(e){
        ...
        // 参数的值 x y width height
        const pre = this.context.getImageData(0, 0, 700, 600)
        // 记录当前操作,便于后续的撤销操作
        this.preHandle.push(pre)
    }
    

    然后完善一下上一步的操作,在这里的时候,因为我们把他压进数组的时候,是先进后出的概念,所以需要从数组的最底部拿到上一次更新的内容,然后将当前的画布的内容,作为下一步的数据存进nextHandle数组中,然后更新到画布上就可以了。

     // 上一步
    handlePre(){
      if(!this.preHandle.length) return false
      const pre =  this.preHandle.pop()
      // 这里应该是把当前的canvas保存进下一步
      const next = this.context.getImageData(0, 0, 760, 610)
      this.nextHandle.push(next)
      this.context.putImageData(pre,0, 0)
    },
    

    下一步的功能跟上一步是一样的,不同的时候这里需要将当前的画布内容存进上一步

     // 下一步
    handleNext(){
      if(!this.nextHandle.length) return false
      const next = this.nextHandle.pop()
      const pre = this.context.getImageData(0, 0, 760, 610)
      this.preHandle.push(pre)
      this.context.putImageData(next,0, 0)
    }
    

    这样上一步下一步的功能也就完成了

    支持添加背景图片,生成图片

    添加背景图片这里有一个麻烦的点,就是添加到画布之后,之前绘画的内容就被覆盖掉了,所以我这里处理的方法是将每次绘制的路径参数都保存了下来,等图片添加完成之后,将之前绘制过的复原回去,这是目前我能想到的方案。
    所以得在之前的handleDownCanvas,handleMove,handleOverMove函数中添加一下操作

    handleDownCanvas(e){
        ...
        // 按下就保存路径位置
        this.lines.push({
            x:canvasX,
            y:canvasY,
            strokeStyle:this.context.strokeStyle,
            shadowColor:this.context.shadowColor
        })
    },
    handleMove(e){
        // 保存路径位置
        this.lines.push({
            x:canvasX,
            y:canvasY,
            strokeStyle:this.context.strokeStyle,
            shadowColor:this.context.shadowColor
        })
    },
    handleOverMove(){
        // 往记录中添加断点
        this.lines.push(null)
    }
    

    然后先看一下整体的代码吧,

    // 选择图片设置
    handleSetImg(){
      let input = document.createElement("input")
      input.type = 'file'
      input.accept = 'image/*'
      input.onchange = this.putImageToCanvas
      input.click()
    },  
    // 更新到canvas
    putImageToCanvas(event){
      const e = event.target;
      const { files } = e; // 拿到所有的文件
      const file = files[0]
    
      let reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onload = () => {
        // console.log('file 转 base64结果:' + reader.result)
        let imag = new Image();
        imag.src = reader.result
        imag.onload = () =>{
          const  {clientWidth,clientHeight} = this.$refs.myPalette
          // 绘制之前还是需要将当前页面添加到上一步
          this.preHandle.push(this.context.getImageData(0, 0, 760, 610))
          this.context.drawImage(imag,0,0,clientWidth,clientHeight)
          // 这里没办法解决画图被覆盖的问题,只能绘制完图片之后将线条绘制回去
          this.resetLine()
        }
      }
    },
    // 重新绘制之前绘画
    resetLine(){
      this.context.beginPath();
      // 这里是将绘制的记录返回回来,但是这里返回之后,就没法再进行上下了
      this.lines.forEach((item,index) => {
        // item === null 代表着抬起手指,断开绘制
        if (item){
          const next_item = this.lines[index+1] ||  item
          this.context.moveTo(item.x,item.y);
          this.context.lineTo(next_item.x,next_item.y);
          this.context.strokeStyle=item.strokeStyle;
          this.context.shadowColor=item.shadowColor
          this.context.stroke();
        }else{
          // 清除子路径
          console.log('清除子路径')
          this.context.beginPath();
        }
      })
    },
    

    handleSetImg
    选择图片,老生常谈的操作了,就没啥好说了
    putImageToCanvas
    这里是将file类型转换为base64,因为不这样做的话,图片加载不出来,然后再进行原来路径的绘制,在这里同样需要把当前的画布内容添加到上一步
    resetLine
    重新绘制之前绘画,这里需要注意的点就是因为在绘制的时候会有断开的行为,所以在判断到当前的item === null的时候,直接调用beginPath()清除子路径操作,然后继续下一步的绘制就行了。

    最后就是生成图片了,这个也没啥好说的,直接上代码吧

     // 生成图片
    createImage(){
      this.image = this.$refs.myPalette.toDataURL("image/png",1)
      console.log("生成图片")
    },
    

    结束

    现在上面说明的功能都已经完成了,具体的代码在github,答应我,点个?再走好吗


    起源地下载网 » canvas : 搞个涂鸦板玩玩吧

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元