最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从表情包来学canvas

    正文概述 掘金(阿隆_趣编程)   2021-02-25   919

    前言

    Hello, 很高兴你看见阿隆的第一篇文章。做前端有一段时间了,最近打算整理一下所学的可视化知识,感兴趣的朋友可以看一下。接下来会慢慢更新,从canvas入门到2d、3d框架等都会一一上菜。

    做一个类似微信的苦涩表情包

    本期将介绍canvas的用法,通过canvas来实现一个表情。以下就是早上摸鱼撸出来的表情了,让我们开始吧。 从表情包来学canvas

    什么是canvas

    canvas是HTML5新增的一个标签元素,通常我们拿来做一些有趣的动画或者合成图像,市面上最常见的应用就是H5活动页,大多数都是用canvas实现的。

    canvas

    画布

    canvas也是一个dom元素, 但要注意画布的宽高由标签上的属性来控制,css的width,height小于标签属性的话会拉伸变形。

    <canvas id="canvas" width="800" height="800">
    	抱歉,您不配拥有canvas
    </canvas>
    

    坐标系统

    canvas默认的坐标系原点是在canvas元素的左上角,向右为X轴的伸展方向,向下为Y轴的伸展方向。 从表情包来学canvas 在控制坐标上,与CSS相似,可以采用translate、rotate、scale、transform等方式。

    1. translate(dx,dy):平移坐标系。相当于把原来位于(0,0)位置的坐标原点平移到(dx、dy)点。
    2. rotate(angle):旋转坐标系。该方法控制坐标系统顺时针旋转angle弧度。
    3. scale(sx,sy):缩放坐标系。该方法控制坐标系统水平方向上缩放sx,垂直方向上缩放sy。
    4. transform(a,b,c,d,e,f):允许缩放、旋转、移动并倾斜当前的环境坐标系,其中a:水平缩放绘图、b:水平倾斜绘图、c:垂直倾斜绘图、d:垂直缩放绘图、e:水平移动绘图、f:垂直移动绘图。
    5. setTransform(a,b,c,d,e,f): 该方法把当前的变换矩阵重置为单位矩阵,然后以相同的参数运行transform。

    常用绘图API

    1. 圆弧
    arc(x, y, r,startAngle, endAngle, anticlosewise)
    // 以(x,y)为圆心 r为半径的圆  绘制startAngle弧度 到endAngle弧度的圆弧 anticlosewise默认为false 即顺时针方向 true为逆时针方向。
    arcTo( x1 , y1 , x2 , y2 , radius ) //根据 两个控制点 (x1,y1) 和 (x2, y2)以及半径绘制弧线 同时连接两个控制点。
    
    1. 路径
    beginPath() 新建一条路径
    moveTo( x, y ) 移动画笔到(x , y)的位置
    closePath() 关闭该路径
    stroke() 将绘制的路径进行描边
    fill() 将绘制的封闭区域进行填充
    
    1. 矩形
    fillRect( x , y , width , height)  //填充以(x,y)为起点宽高分别为width、height的矩形 默认为黑色
    stokeRect( x , y , width , height) //绘制一个空心以(x,y)为起点宽高分别为width、height的矩形
    clearRect( x, y , width , height ) // 清除以(x,y)为起点宽高分别为width、height的矩形 为透明
    
    1. 贝塞尔曲线

    canvas绘制各种图形就是由圆弧、路径、矩形、贝塞尔曲线组成。

    quadraticCurveTo( cp1x, cp1y , x ,y ) // (cp1x,cp1y) 控制点    (x,y)结束点
    bezierCurveTo(cp1x, cp1y ,cp2x , cp2y ,x , y )//(cp1x,cp1y)控制点1   (cp2x,cp2y) 控制点2  (x,y)结束点
    
    1. 渐变色
    let gradient = ctx.createLinearGradient( x1 ,y1 ,x2 ,y2); //线性渐变
    let gradient = ctx.createRadialGradient(x1 ,y1 ,r1 ,x2 ,y2 ,r2);//径向渐变
    gradient.addColorStop( position , color )// position:介于0~1    color:position所处颜色
    
    1. 文本
    fillText(text, x, y, maxWidth) 在(x,y)位置绘制text文本
    strokeText(text,x, y, maxWidth]) 在(x,y)位置绘制text文本边框
    

    状态保存&恢复

    由于上述提到的移动坐标系的方法translate和填充颜色fill等有记录效果,可能会对下一次绘图有影响,所以canvas给出save、restore方法在隔离每次绘图可能产生的影响。

    save()
    // 平移或者绘图
    restore()
    

    表情包案例

    圆脸轮廓

    function face(ctx, options) {
        const { x, y, r } = this.options
        ctx.save()
        ctx.translate(x, y)
        ctx.arc(0,0, r, 0, Math.PI * 2);
        ctx.strokeStyle = this.options.color;
        const radialGradient = ctx.createRadialGradient( -1 * r / 3,  - 1 * r / 3, 4 * r / 5 ,  -1 * r / 3,  -1 * r / 3 , r)
        radialGradient.addColorStop(0, 'rgb(255,255,0)')
        radialGradient.addColorStop(1, 'rgb(255,215,0)')
        ctx.fillStyle = radialGradient
        ctx.fill()
        ctx.stroke();
        ctx.restore();
    }
    
    face(ctx, {
      x: 100,
      y: 100,
      r: 50
    })
    

    从表情包来学canvas

    眼睛

    眼睛的实现分成2个步骤, 先画眼白,利用二次贝塞尔曲线来实现边角的曲线,常见的canvas2d库也是利用这个技巧来实现带圆角的图形。 最后在中心位置点上一个黑点,就完成了眼睛的绘制

    function eye(ctx, options) {
      const radius = options.radius || 4
      const width = options.width || 20
      const height = options.height || 12
      const x = options.x || 0
      const y = options.y || 0
      ctx.save()
      ctx.translate(this.options.x ,this.options.y)
      ctx.strokeStyle = this.options.color
      ctx.fillStyle = '#fff'
      ctx.beginPath();
      ctx.moveTo(0 + radius, 0)
      ctx.lineTo(width - radius, 0)
      ctx.quadraticCurveTo(width ,0, width,  0 + radius)
      ctx.lineTo(width, height -radius)
      ctx.quadraticCurveTo(width, height, width - radius, height)
      ctx.lineTo(0 + radius, height)
      ctx.quadraticCurveTo(0, height, 0, height -radius)
      ctx.lineTo(0, 0 + radius)
      ctx.quadraticCurveTo(0, 0, 0 + radius, 0)
      ctx.closePath()
      ctx.fill()
      ctx.stroke();
      ctx.restore()
    
      ctx.save()
      ctx.fillStyle = 'black'
      ctx.translate(x, y)
      ctx.beginPath()
      ctx.arc(width / 2, height / 2, 3, 0, Math.PI * 2)
      ctx.stroke()
      ctx.fill()
      ctx.restore()
     }
    
    eye(ctx, { // 左眼
      x: 50,
      y: 80
    })
    eye(ctx, { //右眼
     x: 90,
     y: 80
    })
    

    从表情包来学canvas

    嘴巴

    function mouth(ctx, options) {
      const width = options.size || 40
      const height = options.height || 10
      const x = options.x || 40
      const y = options.y || 10
      ctx.save()
      ctx.translate(x, y)
      ctx.beginPath()
      ctx.moveTo(0, 0)
      ctx.quadraticCurveTo(width / 2, height, size, 0)
      ctx.stroke()
      ctx.restore()
    }
    mouth(ctx, {
      x: 60,
      y: 118
    })
    

    从表情包来学canvas

    眼泪

    function tear(ctx, options) {
      const width = options.width || 6
      const height = options.height || 20
      const radius = options.radius || 4
      const x = options.x || 0
      const y = options.y || 0
      ctx.save()
      const gradient = ctx.createLinearGradient(0, 0 , width, height)
      gradient.addColorStop(0, 'rgba(0,191,255,1)')
      gradient.addColorStop(1, 'rgba(135,206,250,1)')
      ctx.fillStyle = gradient
      ctx.translate(this.options.x, this.options.y)
      ctx.beginPath()
      ctx.moveTo(0, 0)
      ctx.lineTo(width, 0)
      ctx.lineTo(width, height - radius)
      ctx.quadraticCurveTo(width, height, width - radius, height)
      ctx.lineTo(0 + radius, height)
      ctx.quadraticCurveTo(0, height, 0, height -radius)
      ctx.lineTo(0, 0)
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
      ctx.restore()
    }
    
    eye(ctx, { // 左眼泪
     x: 90,
     y: 80
    })
    eye(ctx, { // 右眼泪
     x: 90,
     y: 80
    })
    

    从表情包来学canvas 至此,一个简单的表情就实现了。

    简单进阶 - 抽象一下代码

    在常见的canvas库中如pixi.js、create.js,通常图形都会有一个display的基类,用来控制图形的显示隐藏、缩放移动或者图形事件等功能。每个图形都有draw方法,在主类中会依次渲染每个图形 这里简单抽象一下上面的业务代码。

    Display

    // 写点伪代码, 不具体扩展
    class Display {
     options = {}
     constructor(options) {
       Object.assign(this.options, options)
     }
     // 控制隐藏显示
     show(isShow) {
       this.options.show = isShow
     }
     //  图形事件
     on(eventType, callBack) {
     }
    }
    

    Face类

    class Face extends Display {
      constructor(options) {
        super(options)
      }
      draw() {
        const { x, y, r } = this.options
        ctx.save()
        ctx.translate(this.options.x ,this.options.y)
        ctx.arc(x, y, r, 0, Math.PI * 2);
        ctx.strokeStyle = this.options.color;
        const radialGradient = ctx.createRadialGradient(x -1 * r / 3, y - 1 * r / 3, 40, x -1 * r / 3, y -1 * r / 3 , r)
        radialGradient.addColorStop(0, 'rgb(255,255,0)')
        radialGradient.addColorStop(1, 'rgb(255,215,0)')
        ctx.fillStyle = radialGradient
        ctx.fill()
        ctx.stroke();
        ctx.restore();
      }
     }
    

    Eye类

    class Eye extends Display {
      constructor(options) {
        super(options)
      }
    
      draw() {
        const radius = 4
        const width = 20
        const height = 12
        ctx.save()
        ctx.translate(this.options.x ,this.options.y)
        ctx.strokeStyle = this.options.color
        ctx.fillStyle = '#fff'
        ctx.beginPath();
        ctx.moveTo(0 + radius, 0)
        ctx.lineTo(width - radius, 0)
        ctx.quadraticCurveTo(width ,0, width,  0 + radius)
        ctx.lineTo(width, height -radius)
        ctx.quadraticCurveTo(width, height, width - radius, height)
        ctx.lineTo(0 + radius, height)
        ctx.quadraticCurveTo(0, height, 0, height -radius)
        ctx.lineTo(0, 0 + radius)
        ctx.quadraticCurveTo(0, 0, 0 + radius, 0)
        ctx.closePath()
        ctx.fill()
        ctx.stroke();
        ctx.restore()
    
        ctx.save()
        ctx.fillStyle = 'black'
        ctx.translate(this.options.x ,this.options.y)
        ctx.beginPath()
        ctx.arc(width / 2, height / 2, 3, 0, Math.PI * 2)
        ctx.stroke()
        ctx.fill()
        ctx.restore()
      }
    }
    

    Mouth

    class Mouth extends Display {
      constructor(options) {
        super(options)
      }
      draw() {
        const size = 40
        const height = 10
        ctx.save()
        ctx.translate(this.options.x, this.options.y)
        ctx.beginPath()
        ctx.moveTo(0, 0)
        ctx.quadraticCurveTo(size / 2, height, size, 0)
        ctx.stroke()
        ctx.restore()
      }
    }
    
    Tear类
    class Tear extends Display{
      constructor(options) {
        super(options)
      }
    
      draw() {
        const { width , height, radius } = this.options
        ctx.save()
        const gradient = ctx.createLinearGradient(0, 0 , width, height)
        gradient.addColorStop(0, 'rgba(0,191,255,1)')
        gradient.addColorStop(1, 'rgba(135,206,250,1)')
        ctx.fillStyle = gradient
        ctx.translate(this.options.x, this.options.y)
        ctx.beginPath()
        ctx.moveTo(0, 0)
        ctx.lineTo(width, 0)
        ctx.lineTo(width, height - radius)
        ctx.quadraticCurveTo(width, height, width - radius, height)
        ctx.lineTo(0 + radius, height)
        ctx.quadraticCurveTo(0, height, 0, height -radius)
        ctx.lineTo(0, 0)
        ctx.closePath()
        ctx.fill()
        ctx.stroke()
        ctx.restore()
      }
    }
    

    主渲染代码

    let face = new Face({
        x: 100,
        y: 100,
        r: 50,
        color: 'rgba(0,0,0,0.2)'
      })
    
      const eye1 = new Eye({
        x: 50,
        y: 80,
        color: 'rgba(0,0,0,1)'
      })
    
      const eye2 = new Eye({
        x: 90,
        y: 80,
        color: 'rgba(0,0,0,1)'
      })
    
      const tearDefault = {
        width: 6,
        height: 20,
        radius: 4
      }
      const tear1 = new Tear({
        x: 57,
        y: 92,
        ...tearDefault
      })
    
      const tear2 = new Tear({
        x: 98,
        y: 92,
        ...tearDefault
      })
    
      const mouth = new Mouse({
        x: 60,
        y: 118,
        color: 'rgba(0,0,0,1)'
      })
      const graphics = []
      graphics.push(face)
      graphics.push(eye1)
      graphics.push(eye2)
      graphics.push(mouth)
      graphics.push(tear1)
      graphics.push(tear2)
      function render() {
        graphics.forEach(graphic => {
          graphic.draw()
        })
      }
      render()  
    

    最后

    本期分享了一个简单的canvas案例, 想控制一下篇幅长度,没有加上动画,这里补充一下,用tween.js可以轻易的实现让上面表情流眼泪的效果噢,感兴趣的朋友可以自己实践一下。 代码地址

    下期更新 从英雄联盟来学pixi.js


    起源地下载网 » 从表情包来学canvas

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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