最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • SpriteJS —— Canvas动画从未如此简单

    正文概述 掘金(十年踪迹)   2020-11-30   567

    SpriteJS是一款由360奇舞团开源的跨终端canvas绘图库,可以基于canvas快速绘制结构化UI、动画和交互效果,并发布到任何拥有canvas环境的平台上(比如浏览器、小程序和node)。

    JS Bin on jsbin.com

    我们知道,Canvas Api可以很灵活地绘制各种矢量图形到画布上,但是Canvas Api本身比较低级,比如我们要在画布中央绘制一个带有圆角的红色矩形,使用Canvas原生的Api,需要这样:

    JSBIN

    const canvas = document.getElementById('paper'),
          context = canvas.getContext('2d')
    
    const [x, y, w, h, r] = [200, 200, 200, 200, 50]
    
    context.fillStyle = 'red'
    context.beginPath()
    context.moveTo(x + r, y)
    context.arcTo(x + w, y, x + w, y + h, r)
    context.arcTo(x + w, y + h, x, y + h, r)
    context.arcTo(x, y + h, x, y, r)
    context.arcTo(x, y, x + w, y, r)
    context.closePath()
    context.fill()
    

    如果实现相同的效果,使用SpriteJS是这样写:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      anchor: 0.5,
      bgcolor: 'red',
      pos: [300, 300],
      size: [200, 200],
      borderRadius: 50,
    })
    
    layer.append(s)
    

    Sprite为图形创建类似于DOM的对象模型,因此我们可以像创建DOM元素一样,创建Sprite元素,并将它们append到layer上,从而将元素呈现到画布上。

    SpriteJS 的特点

    • 基于canvas绘制的文档对象模型
    • 四种基本精灵类型:Sprite、Path、Label、Group
    • 支持基础和高级的精灵属性,精灵盒模型、属性与CSS3具有高度一致性。
    • 简便而强大的Transition、Animation API
    • 支持雪碧图和资源预加载
    • 可扩展的事件机制
    • 高性能的缓存策略
    • 对D3、Matter-js、Proton和其他第三方库友好
    • 跨平台,支持node-canvas、微信小程序

    Sprite 文档结构

    SpriteJS —— Canvas动画从未如此简单

    SpriteJS支持设置精灵元素常用的基本属性,包括:

    • archor: 锚点,定义元素坐标的参考点,[0,0]是左上角,[1,1]是右下角
    • size([x,y]): 精灵元素的大小
    • pos([width,height]): 精灵元素的位置
    • id: 元素的ID
    • name: 元素的name
    • bgcolor: 背景颜色
    • border: 边框
    • borderRadius: 圆角
    • padding: 同css的padding
    • zIndex: 同css的zIndex
    • textures: 精灵图片
    • filter: 滤镜
    • offsetPath: 同css3的offsetPath
    • offsetDistance: 同css3的offsetDistance
    • rotate: 旋转角度
    • scale: 缩放
    • translate: 平移
    • skew: 倾斜
    • transform: 同css3的transform

    如果只是绘制静态图形,SpriteJS还体现不出优势,但如果要给图形增加动画效果,那么SpriteJS内置了Transition API和标准的Web Animation API

    比如我们要让上面的圆角矩形的颜色从红色过度到绿色,只需要:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      anchor: 0.5,
      bgcolor: 'red',
      pos: [300, 300],
      size: [200, 200],
      borderRadius: 50,
    })
    
    layer.append(s)
    
    s.transition(2.0).attr({bgcolor: 'green'})
    

    我们可以同时对多个属性应用Transition:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      anchor: 0.5,
      bgcolor: 'red',
      pos: [300, 300],
      size: [200, 200],
      borderRadius: 50,
    })
    
    layer.append(s)
    
    s.transition(2.0)
     .attr({
      bgcolor: 'green',
      width: width => width + 100,
     })
    

    而且Transition本身返回Promise,所以我们可以方便地实现连续的Transition动画:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      anchor: 0.5,
      bgcolor: 'red',
      pos: [300, 300],
      size: [200, 200],
      borderRadius: 50,
    })
    
    layer.append(s)
    
    (async function(){
      await s.transition(2.0)
        .attr({
          bgcolor: 'green',
          width: width => width + 100,
         })
      await s.transition(1.0)
        .attr({
          bgcolor: 'yellow',
          height: height => height + 100,    
        })
    }())
    

    除了简单的Transition动画之外,我们可以使用浏览器标准的Web Animation API来实现更加复杂的动画,对于Web Animation API或CSS3 Animation比较熟悉的同学应该对它比较熟悉:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      anchor: 0.5,
      bgcolor: 'red',
      pos: [300, 300],
      size: [200, 200],
      borderRadius: 50,
    })
    
    layer.append(s)
    
    s.animate([
      {rotate: 0, borderRadius: 50, bgcolor: 'red'},
      {rotate: 360, borderRadius: 0, bgcolor: 'green', offset: 0.7},
      {rotate: 720, borderRadius: 50, bgcolor: 'blue'}
    ], {
      duration: 3000,
      iterations: Infinity,
      direction: 'alternate',
      easing: 'ease-in-out',
    })
    

    SpriteJS支持所有的标准Web Animation API的timing选项,另外我们也可以使用规范中定义的animaton.finished的Promise方便地实现顺序的动画:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      anchor: 0.5,
      bgcolor: 'red',
      pos: [100, 100],
      size: [50, 50],
    })
    
    const s2 = s.cloneNode(true)
    s2.attr({
      y: 500,
    })
    
    layer.append(s, s2)
    
    ;(async function(){
      await s.animate([
        {x: 500, offset: 0.5},
        {rotate: 45, bgcolor: 'blue'},
      ], {
        duration: 2000,
        fill: 'forwards',
      }).finished
    
      await s2.animate([
        {x: 500, offset: 0.5},
        {rotate: 45, bgcolor: 'green'},
      ], {
        duration: 2000,
        fill: 'forwards',
      }).finished
     
      await Promise.all([s.animate([
        {y: 500}
      ], {
        duration: 2000,
        fill: 'forwards',
      }).finished, s2.animate([
        {y: 100}
      ], {
        duration: 2000,
        fill: 'forwards',
      }).finished])
      
      await Promise.all([s, s2].map(s => s.animate([
        {x: 100}
      ], {
        duration: 2000,
        fill: 'forwards',    
      }).finished))
      
      await s.animate([
        {rotate: -360, bgcolor: 'red'}
      ], {
        duration: 1000,
        fill: 'forwards',    
      }).finished
    
      await s2.animate([
        {rotate: -360, bgcolor: 'red'}
      ], {
        duration: 1000,
        fill: 'forwards',    
      }).finished
    }())
    

    Sprite texture

    我们可以给Sprite元素绑定图片,只需要将图片的URL作为元素的textures属性。

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const s = new spritejs.Sprite({
      textures: 'https://p0.ssl.qhimg.com/t01a72262146b87165f.png',
      anchor: 0.5,
      pos: [300, 300],
      scale: 0.5,
    })
    
    layer.append(s)
    

    注意这里使用textures而不是texture作为属性名,因为sprite对象允许我们传多张图片进去,绘制的时候会将这些图片依次叠加。

    我们还可以给多张图片指定rect和srcRect:

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const texture = 'https://p5.ssl.qhimg.com/t01a2bd87890397464a.png'
    
    const s = new spritejs.Sprite()
    s.attr({
      anchor: 0.5,
      textures: [
        {src: texture, rect: [0, 0, 190, 268], srcRect: [0, 0, 190, 268]},
        {src: texture, rect: [200, 278, 190, 268], srcRect: [191, 269, 190, 268]},
        {src: texture, rect: [0, 278, 190, 268], srcRect: [0, 269, 190, 268]},
        {src: texture, rect: [200, 0, 190, 268], srcRect: [191, 0, 190, 268]},
      ],
      border: [2, 'grey'],
      pos: [300, 300],
    })
    
    layer.append(s)
    

    使用textures的时候,我们支持资源的预加载和雪碧图,我们可以将资源使用TexturePacker进行打包(可使用TP的免费版,导出配置文件格式为JSON Hash)

    例子

    (async function () {
      const {Scene, Sprite} = spritejs
      const scene = new Scene('#paper', {viewport: ['auto', 'auto'], resolution: [1200, 1200]})
    
      await scene.preload([
        'https://p3.ssl.qhimg.com/t010ded517024020e10.png',
        'https://s1.ssl.qhres.com/static/6ead70a354da7aa4.json',
      ])
      
      ...
      
      const layer = scene.layer('fglayer')
    	
      ...
      
      const head = new Sprite('head.png')
      head.attr({
        pos: [606, 0],
      })
    
      const neck = new Sprite('neck.png')
      neck.attr({
        pos: [626, 68],
        zIndex: -1,
      })
    
      const body = new Sprite('body.png')
      body.attr({
        pos: [606, 73],
      })
    
      const leftArm = new Sprite('arm-1.png')
      leftArm.attr({
        pos: [600, 73],
      })
    
      const rightArm = new Sprite('arm-2.png')
      rightArm.attr({
        pos: [675, 73],
      })
      
      ...
    }())
    

    绘制矢量图

    除了普通的Sprite类型之外,SpriteJS提供绘制矢量图的Path类型

    例子

    const scene = new Scene('#svgpath', {viewport: ['auto', 'auto'], resolution: [1540, 600]})
    const layer = scene.layer('fglayer')
    
    const p1 = new Path()
    p1.attr({
      path: {
        d: 'M280,250A200,200,0,1,1,680,250A200,200,0,1,1,280,250Z',
        transform: {
          scale: 0.5,
        },
        trim: true,
      },
      strokeColor: '#033',
      fillColor: '#839',
      lineWidth: 12,
      pos: [100, 50],
    })
    
    layer.appendChild(p1)
    
    const p2 = new Path()
    p2.attr({
      path: {
        d: 'M480,50L423.8,182.6L280,194.8L389.2,289.4L356.4,430L480,355.4L480,355.4L603.6,430L570.8,289.4L680,194.8L536.2,182.6Z',
        transform: {
          rotate: 45,
        },
        trim: true,
      },
      fillColor: '#ed8',
      pos: [450, 100],
    })
    layer.appendChild(p2)
    
    const p3 = new Path()
    p3.attr({
      path: {
        d: 'M480,437l-29-26.4c-103-93.4-171-155-171-230.6c0-61.6,48.4-110,110-110c34.8,0,68.2,16.2,90,41.8C501.8,86.2,535.2,70,570,70c61.6,0,110,48.4,110,110c0,75.6-68,137.2-171,230.8L480,437z',
        trim: true,
      },
      strokeColor: '#f37',
      lineWidth: 20,
      lineJoin: 'round',
      lineCap: 'round',
      pos: [1000, 100],
    })
    layer.appendChild(p3)
    

    Path能够支持使用SVG Path来在Canvas上绘制矢量图形:

    SVG和Sprite Path:

    <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
      <path d="M 10,30
               A 20,20 0,0,1 50,30
               A 20,20 0,0,1 90,30
               Q 90,60 50,90
               Q 10,60 10,30 z"/>
    </svg>
    

    JSBIN

    const p = new spritejs.Path(`M 10,30
               A 20,20 0,0,1 50,30
               A 20,20 0,0,1 90,30
               Q 90,60 50,90
               Q 10,60 10,30 z`)
    
    p.attr({
    	fillColor: 'red',
    	pos: [200, 200],
    })
    
    layer.append(p)
    

    SpriteJS还能够支持Path的transition(或animate)

    JSBIN

    const scene = new spritejs.Scene('#container'),
          layer = scene.layer()
    
    const paths = [
      "M280,250A200,200,0,1,1,680,250A200,200,0,1,1,280,250Z",
      "M480,50L423.8,182.6L280,194.8L389.2,289.4L356.4,430L480,355.4L480,355.4L603.6,430L570.8,289.4L680,194.8L536.2,182.6Z",
      "M480,437l-29-26.4c-103-93.4-171-155-171-230.6c0-61.6,48.4-110,110-110c34.8,0,68.2,16.2,90,41.8C501.8,86.2,535.2,70,570,70c61.6,0,110,48.4,110,110c0,75.6-68,137.2-171,230.8L480,437z",
      "M595,82.1c1,1-1,2-1,2s-6.9,2-8.9,4.9c-2,2-4.9,8.8-4.9,8.8c3.9-1,8.9-2,13.8-4c1,0,2,1,3,2c1,0-11.8,4.9-14.8,6.9c-2,2-11.8,9.9-14.8,9.9c-2.9,0-9.9,1-9.9,1c1,2,2,3.9,3.9,6.9c0,0-6.9,4-6.9,4.9c-1,1-5.9,6.9-5.9,6.9s17.7,1.9,23.6-7.9c-5.9,9.8-19.7,19.7-48.2,19.7c-29.5,0-53.1-11.8-68.9-17.7c-16.7-6.9-38.4-14.8-56.1-14.8c-16.7,0-36.4,4.9-49.2,16.8c-22.6-8.8-54.1-4-68.9,9.8c-13.8,13.8-27.5,30.5-29.5,42.3c-2.9,12.9-9.8,43.3-19.7,57.2c-13.8,22.5-29.5,28.5-34.5,38.3c-4.9,9.9-4.9,30.5-4,30.5c2,1,8.9,0,12.8-2c7.9-2.9,29.5-25.6,37.4-36.4c7.9-10.9,34.5-58.1,38.4-74.8s7.9-33.5,19.7-42.3c12.8-8.8,28.5-4.9,28.5-3.9c0,0-14.7,11.8-15.7,44.3s18.7,28.6,8.8,49.2c-9.9,17.7-39.3,5.9-49.2,16.7c-7.9,8.9,0,40.3,0,46.2c0,6-3,33.5-4.9,40.4c-1,5.9,0,9.8-1,13.8c-1,3,6,3.9,6,3.9s-6,7.8-8.9,5.9c-2.9-1-4.9-1-6.9,0c-2,0-5.9,1.9-9.9,0L232.9,401c2,1,4.9,1.9,7.9,1c4-1,23.6-9.9,25.6-11.9c2.9-1,19.7-10.8,22.6-16.7c2-5.9,5.9-24.6,5.9-30.5c1-6,2-24.6,2-29.5s-1-13.8,0-17.7c2-2.9,4.9-6.9,8.9-8.9c4.9-1,10.8-1,11.8-1c2,0,18.7,2,21.6,2c3.9,0,19.7-2.9,23.6-5c4.9-0.9,7.8,0,8.9,2c2,1.9-2,4.9-2,5.9c-1,1-8.8,10.8-10.8,14.7c-2,4.9-8.8,13.8-6.9,17.7c2,3.9,2,4.9,7.8,7.9c5.9,1.9,28.5,13.8,41.3,25.6c13.8,12.7,26.6,28.4,28.6,36.4c2.9,8.9,7.8,9.8,10.8,9.8c3,1,8.9,2,8.9,5.9s-1,8.8-1,8.8l36.4,13.8c0,0,0-12.8-1-17.7c-1-5.9-6.9-11.8-11.8-17.7c-4.9-6.9-56-57.1-61-61c-4.9-3-8.9-6.9-9.8-14.7c-1-7.9,8.8-13.8,14.8-20.6c3.9-4.9,14.7-27.6,16.7-30.6c2-2.9,8.9-10.8,12.8-10.8c4.9,0,15.8,6.9,29.5,11.8c5.9,2,48.2,12.8,54.1,14.8c5.9,1,18.6,0,22.6,3.9c3.9,2.9,0,10.9-1,15.8c-1,5.9-11.8,27.5-11.8,27.5s2,7.8,2,13.8c0,6.9-2.9,31.5-5.9,39.3c-2,8.9-15.8,31.6-18.7,35.5c-2,2.9-4.9,4.9-4.9,9.9c0,4.9,8.8,6,11.8,9.8c4,3,1,8.8,0,14.8l39.4,16.7c0-2.9,2-7.9,0-9.9c-1-2.9-5.9-8.8-8.8-12.8c-2-2.9-8.9-13.8-10.8-15.8c-2-2.9-2-8.8,0-13.8c1-4.9,13.8-38.3,14.7-42.3c2-4.9,20.7-44.3,22.6-49.2c2-5.9,17.7-34.4,19.7-39.4c2-5.9,14.8-10.8,18.7-10.8c4.9,0,29.5,8.8,33.4,10.8c2.9,1,25.6,10.9,29.5,12.8c4.9,1.9,2,5.9-1,6.9c-2.9,1.9-39.4,26.5-42.3,27.5c-2.9,1-5.9,3.9-7.9,3.9c-2.9,0-6.9,3.1-6.9,4c0,2-1,5.9-5.9,5.9c-3.9,0-11.8-5.9-16.7-11.8c-6.9,3.9-11.8,6.9-14.8,12.8c-4.9,4.9-6.9,8.9-9.8,15.8c2,2,5.9,2.9,8.8,2.9h31.5c3,0,6.9-0.9,9.9-1.9c2.9-2,80.7-53.1,80.7-53.1s12.8-9.9,12.8-18.7c0-6.9-5.9-8.9-7.9-11.8c-3-1.9-20.7-13.8-23.6-15.7c-4-2.9-17.7-10.9-21.6-12.9c-3-1.9-13.8-5.8-13.8-5.8c3-8.9,5-15.8,5.9-17.7c1-2,1-19.7,2-22.7c0-2.9,5-15.7,6.9-17.7c2-2,6.9-17.7,7.9-20.7c1-1.9,8.8-24.6,12.8-24.6c3.9-1,7.9,2.9,11.8,2.9c4,1,18.7-1,26.6,0c6.9,1,15.8,9.9,17.7,10.8c2.9,1,9.8,3.9,11.8,3.9c1,0,10.8-6.9,10.8-8.8c0-2-6.9-5.9-7.9-5.9c-1-1-7.8-4.9-7.8-4.9c0,1,2.9-1.9,7.8-1.9c3.9,0,7.9,3.9,8.8,4.9c2,1,6.9,3.9,7.9,1.9c1-1,4.9-5.9,4.9-8.9c0-4-3.9-8.8-5.9-10.8s-24.6-23.6-26.6-24.6c-2.9-1-14.7-11.8-14.7-14.7c-1-2-6.9-6.9-7.9-7.9s-30.5-21.6-34.5-24.6c-3.9-2.9-7.9-7.8-7.9-12.7s-2-17.7-2-17.7s-6.9-1-9.8,1.9c-2.9,2-9.8,17.8-13.8,17.8c-10.9-2-24.6,1-24.6,2.9c1,2.9,10.8,1,10.8,1c0,1-3.9,5.9-6.9,5.9c-2,0-7.8,2-8.8,2.9c-2,0-5.9,3.1-5.9,3.1c2.9,0,5.9,0,9.8,0.9c0,0-5.9,4-8.9,4c-2.9,0-12.8,2.9-15.7,3.9c-2,1.9-9.9,7.9-9.9,7.9H589l1,2h4.9L595,82.1L595,82.1z",
      "M638.9,259.3v-23.8H380.4c-0.7-103.8-37.3-200.6-37.3-200.6s-8.5,0-22.1,0C369.7,223,341.4,465,341.4,465h22.1c0,0,11.4-89.5,15.8-191h210.2l11.9,191h22.1c0,0-5.3-96.6-0.6-205.7H638.9z",
      "M345.47,250L460.94,450L230,450Z M460.94,50L576.41,250L345.47,250Z M576.41,250L691.88,450L460.94,450Z",
    ]
    const p = new spritejs.Path({d: paths[0], scale: 0.5})
    
    p.attr({
      fillColor: 'blue',
      pos: [0, 0],
    })
    
    layer.append(p)
    
    let i = 0
    
    function wait(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms)
      })
    }
    
    (async function(){
      // noprotected
      for(let i = 0; i < 100; i++) {
        await p.transition(2.0)
         .attr({d: paths[++i % paths.length]})
        await wait(1000)
      }  
    }())
    

    分组

    除了Sprite、Path外,SpriteJS支持Group元素,可以将多个Sprite设置成一组,进行统一的动画。

    例子

    (function () {
      const {Scene, Path, Group} = spritejs
      const scene = new Scene('#paper', {viewport: ['auto', 'auto'], resolution: [1200, 1200]})
    
      const layer = scene.layer('fglayer')
      layer.canvas.style.backgroundColor = '#9cd470'
    
      const d = 'M235.946483,75.0041277 C229.109329,53.4046689 214.063766,34.845093 195.469876,22.3846101 C175.428247,8.9577702 151.414895,2 127.314132,2 C75.430432,2 31.6212932,32.8626807 18.323944,74.9130141 C8.97646468,77.1439182 2,85.5871171 2,95.7172992 C2,104.709941 7.49867791,112.371771 15.2700334,115.546944 C15.8218133,115.773348 16.6030463,122.336292 16.8270361,123.236385 C22.1235768,144.534892 35.4236577,163.530709 52.5998558,176.952027 C52.6299032,176.976876 52.6626822,177.001726 52.6954612,177.026575 C72.5513428,192.535224 98.5478246,202 127.043705,202 C152.034964,202 176.867791,194.597706 197.428422,180.146527 C215.659011,167.335395 230.201962,148.621202 236.52831,126.969284 C237.566312,123.421373 238.549682,119.685713 239.038636,116.019079 C239.044099,115.983185 239.074146,115.444787 239.082341,115.442025 C246.673412,112.184022 252,104.580173 252,95.7172992 C252,85.6892748 245.15192,77.3371896 235.946483,75.0041277'
      const shadowD = 'M82.1534529,43 C127.525552,43 164.306906,33.6283134 164.306906,21.663753 C164.306906,9.6991926 127.525552,0 82.1534529,0 C36.7813537,0 0,9.6991926 0,21.663753 C0,33.6283134 36.7813537,43 82.1534529,43 Z'
      const shadow = new Path()
      shadow.attr({
        path: shadowD,
        fillColor: '#000000',
        opacity: 0.05,
        pos: [500, 734],
        anchor: 0.5,
        scale: [1.3, 1.2]
      })
      layer.append(shadow)
    
      const lemon = new Path()
      lemon.attr({
        path: {d},
        anchor: 0.5,
        pos: [500, 600],
        fillColor: '#fed330',
        scale: 1.4
      })
      layer.append(lemon)
    
      const lemonGroup = new Group()
      lemonGroup.attr({
        anchor: 0.5,
        pos: [610, 600],
        size: [180, 180],
        bgcolor: '#faee35',
        border: [6, '#fdbd2c'],
        borderRadius: 90,
        scale: 1.5
      })
      layer.append(lemonGroup)
    
      const d2 = 'M0,0L0,100A15,15,0,0,0,50,86.6z'
      for(let i = 0; i < 12; i++) {
        const t = new Path()
        t.attr({
          path: {d: d2, transform: {scale: 0.65}},
          pos: [90, 90],
          lineWidth: 2,
          lineCap: 'round',
          lineJoin: 'round',
          strokeColor: '#fff',
          fillColor: '#f8c32d',
          rotate: 30 * i,
        })
        lemonGroup.append(t)
      }
    
      lemonGroup.animate([
        {rotate: 360},
      ], {
        duration: 10000,
        iterations: Infinity,
      })
    
      lemonGroup.on('mouseenter', (evt) => {
        layer.timeline.playbackRate = 3.0
      })
      lemonGroup.on('mouseleave', (evt) => {
        layer.timeline.playbackRate = 1.0
      })
    }())
    

    分组可以嵌套,这样我们就可以用分组元素组合成复杂的UI组件。

    响应事件

    SpriteJS不只是能够给精灵元素添加动画,还能像操作DOM元素那样够给精灵元素注册事件。精灵元素支持基本的mouse事件和touch事件,这些事件被SpriteJS的scene代理给对应的精灵:

    例子

    const scene = new Scene('#dom-events', {viewport: ['auto', 'auto'], resolution: [1540, 600]})
    const layer = scene.layer('fglayer')
    
    const s1 = new Sprite()
    s1.attr({
      anchor: [0.5, 0.5],
      pos: [770, 300],
      size: [300, 300],
      rotate: 45,
      bgcolor: '#3c7',
    })
    
    layer.append(s1)
    
    s1.on('mouseenter', (evt) => {
      s1.attr('border', [4, 'blue'])
    })
    s1.on('mouseleave', (evt) => {
      s1.attr('border', [0, ''])
    })
    
    const anchorCross = new Path('M0,10H10,20M10,0V10,20')
    anchorCross.attr({
      anchor: [0.5, 0.5],
      pos: [770, 300],
      strokeColor: 'red',
      rotate: 45,
      lineWidth: 4,
    })
    
    layer.append(anchorCross)
    
    const label = new Label('鼠标位置:')
    
    label.attr({
      pos: [20, 50],
      font: '32px Arial',
      lineHeight: 56,
    })
    
    layer.append(label)
    
    layer.on('mousemove', (evt) => {
      const {x, y, targetSprites} = evt
    
      label.text = `鼠标位置:\n相对于 layer: ${Math.round(x)}, ${Math.round(y)}`
    
      if(targetSprites.length && targetSprites.includes(s1)) {
        const [offsetX, offsetY] = s1.pointToOffset(x, y).map(Math.round)
        label.text += `\n相对于元素:${offsetX}, ${offsetY}`
      }
    })
    

    默认代理的事件:

    • mousedown
    • mouseup
    • mousemove
    • mouseenter
    • mouseleave
    • touchstart
    • touchend
    • touchmove

    SpriteJS还可以代理其他事件,比如keyboard事件,我们可以为keyboard事件改写精灵的事件检测函数pointCollision

    例子

    const scene = new Scene('#event-delegate', {viewport: ['auto', 'auto'], resolution: [1540, 600]})
    const layer = scene.layer()
    
    class KeyButton extends Label {
      pointCollision(evt) {
        return evt.originalEvent.key === this.text
      }
    }
    KeyButton.defineAttributes({
      init(attr) {
        attr.setDefault({
          font: '42px Arial',
          border: {width: 4, color: 'black', style: 'solid'},
          width: 50,
          height: 50,
          anchor: [0.5, 0.5],
          textAlign: 'center',
          lineHeight: 50,
        })
      },
    })
    
    const keys = [
      'qwertyuiop',
      'asdfghjkl',
      'zxcvbnm',
    ]
    for(let i = 0; i < 3; i++) {
      const keyButtons = [...keys[i]]
      for(let j = 0; j < keyButtons.length; j++) {
        const key = new KeyButton(keyButtons[j])
        key.attr({
          pos: [250 + j * 80, 200 + i * 100],
        })
        key.on('keydown', (evt) => {
          key.attr({
            bgcolor: 'grey',
            fillColor: 'white',
          })
        })
        key.on('keyup', (evt) => {
          key.attr({
            bgcolor: 'transparent',
            fillColor: 'black',
          })
        })
        layer.append(key)
      }
    }
    
    const label = new Label('轻敲键盘')
    label.attr({
      anchor: [0.5, 0],
      pos: [770, 50],
      font: '42px Arial',
    })
    layer.append(label)
    
    scene.delegateEvent('keydown', document)
    scene.delegateEvent('keyup', document)
    

    关于事件处理更多的内容,可以查看文档

    SpriteJS和D3

    由于SpriteJS的Api和DOM拥有高度一致性,因此它对D3友好,可以方便地使用D3+SpriteJS实现数据可视化展现。

    用SpriteJS+D3实现柱状图

    (function () {
      const paper = new spritejs.Scene('#paper', {
        viewport: ['auto', 'auto'],
        resolution: [1600, 1200],
        stickMode: 'width',
      })
    
      const dataset = [125, 121, 127, 193, 309]
    
      const linear = d3.scaleLinear()
        .domain([100, d3.max(dataset)])
        .range([0, 500])
    
      const colors = ['#fe645b', '#feb050', '#c2af87', '#81b848', '#55abf8']
    
      const s = d3.select(paper).append('fglayer')
    
      const chart = s.selectAll('sprite')
        .data(dataset)
        .enter()
        .append('sprite')
        .attr('x', 450)
        .attr('y', (d, i) => {
          return 200 + i * 95
        })
        .attr('width', 0)
        .attr('height', 80)
        .attr('bgcolor', '#ccc')
    
      chart.transition()
        .duration(2000)
        .attr('width', (d, i) => {
          return linear(d)
        })
        .attr('bgcolor', (d, i) => {
          return colors[i]
        })
    
      s.append('axis')
        .attr('ticks', [100, 200, 300, 400])
        .attr('axisScales', [linear])
        .attr('direction', 'bottom')
        .attr('pos', [450, 700])
        .attr('color', '#666')
    
      chart.on('click', (data) => {
        /* eslint-disable no-console */
        console.log(data, d3.event)
        /* eslint-enable no-console */
      })
    }())
    

    更多的例子见:SpriteJS+D3

    物理引擎

    SpriteJS的扩展让它能够支持matter-js

    例子

    // module aliases
    const {Engine, World, Composites, Composite, Bodies} = Matter
    
    // create an engine
    const engine = Engine.create()
    // engine.world.gravity.scale = 0; //turn off gravity (it's added back in later)
    
    const stackA = Composites.stack(100, 100, 6, 6, 0, 0, (x, y) => {
      return Bodies.rectangle(x, y, 15, 15, {
        // friction: 0,
        // frictionAir: 0,
        // frictionStatic: 0,
        // restitution: 1
      })
    })
    
    const wall = Bodies.rectangle(400, 300, 500, 20, {
      isStatic: true,
    })
    
    World.add(engine.world, [stackA, wall])
    
    const offset = 5
    World.add(engine.world, [
      Bodies.rectangle(400, -offset, 800 + 2 * offset, 50, {
        isStatic: true,
      }),
      Bodies.rectangle(400, 600 + offset, 800 + 2 * offset, 50, {
        isStatic: true,
      }),
      Bodies.rectangle(800 + offset, 300, 50, 600 + 2 * offset, {
        isStatic: true,
      }),
      Bodies.rectangle(-offset, 300, 50, 600 + 2 * offset, {
        isStatic: true,
      }),
    ])
    
    const scene = new Scene('#simple-demo', {viewport: ['auto', 'auto'], resolution: [800, 600]})
    const fglayer = scene.layer('fglayer')
    
    const blocks = []
    
    function render() {
      Engine.update(engine, 16)
      const bodies = Composite.allBodies(engine.world)
      // console.log(bodies)
      for(let i = 0; i < bodies.length; i++) {
        const body = bodies[i],
          {position, angle} = body
        const pos = [
            Math.round(position.x * 10) / 10,
            Math.round(position.y * 10) / 10,
          ],
          rotate = Math.round(180 * angle * 10 / Math.PI) / 10
    
        let block = blocks[i]
        if(!block) {
          const {min, max} = body.bounds
          block = new Sprite()
          block.attr({
            anchor: 0.5,
            size: [max.x - min.x, max.y - min.y],
            pos,
            rotate,
            bgcolor: body.render.fillStyle,
          })
          blocks[i] = block
          fglayer.append(block)
        } else {
          block.attr({
            pos,
            rotate,
          })
        }
      }
      window.requestAnimationFrame(render)
    }
    
    render()
    

    粒子系统

    同样,通过扩展,SpriteJS支持Proton粒子

    JSBIN

    const {Scene, ProtonRenderer} = spritejs
    const scene = new Scene('#container', {
      viewport: [600, 600],
      resolution: [600, 600],
    })
    const layer = scene.layer('fglayer')
    
    const proton = new Proton()
    const emitter = new Proton.Emitter()
    
    // set Rate
    emitter.rate = new Proton.Rate(Proton.getSpan(10, 20), 0.1)
    
    // add Initialize
    emitter.addInitialize(new Proton.Radius(1, 12))
    emitter.addInitialize(new Proton.Life(2, 4))
    emitter.addInitialize(new Proton.Velocity(3, Proton.getSpan(0, 360), 'polar'))
    
    // add Behaviour
    emitter.addBehaviour(new Proton.Color('#ff0000', 'random'))
    emitter.addBehaviour(new Proton.Alpha(1, 0))
    
    // set emitter position
    emitter.p.x = layer.canvas.width / 2
    emitter.p.y = layer.canvas.height / 2
    emitter.emit(5)
    
    // add emitter to the proton
    proton.addEmitter(emitter)
    
    // add canvas renderer
    const renderer = new ProtonRenderer(layer)
    proton.addRenderer(renderer)
    
    // use Euler integration calculation is more accurate (default false)
    Proton.USE_CLOCK = false
    // proton.update()
    function tick() {
      requestAnimationFrame(tick)
      proton.update()
    }
    tick()
    

    更多粒子见SpriteJS+Proton

    外部时钟

    SpriteJS支持外部时钟,这使得它可以很容易与其他效果库一起使用,比如下面的例子演示了将SpriteJS与AlloyTeam的Curvejs一同使用:

    例子

    ;(async function () {
      const birdsJsonUrl = 'https://s5.ssl.qhres.com/static/5f6911b7b91c88da.json'
      const birdsRes = 'https://p.ssl.qhimg.com/d/inn/c886d09f/birds.png'
    
      const scene = new Scene('#curvejs', {
        resolution: [1540, 600],
        viewport: 'auto',
      })
      const layer = scene.layer('fglayer', {
        autoRender: false,
      })
      await scene.preload([birdsRes, birdsJsonUrl])
      const s = new Sprite('bird1.png')
    
      s.attr({
        anchor: [0.5, 0.5],
        pos: [300, 100],
        transform: {
          scale: [0.5, 0.5],
        },
        offsetPath: 'M10,80 q100,120 120,20 q140,-50 160,0',
        zIndex: 200,
      })
      s.animate([
        {offsetDistance: 0},
        {offsetDistance: 1},
      ], {
        duration: 3000,
        direction: 'alternate',
        iterations: Infinity,
      })
    
      s.animate([
        {scale: [0.5, 0.5], offsetRotate: 'auto'},
        {scale: [0.5, -0.5], offsetRotate: 'reverse'},
        {scale: [0.5, 0.5], offsetRotate: 'auto'},
      ], {
        duration: 6000,
        iterations: Infinity,
        easing: 'step-end',
      })
      s.animate([
        {textures: 'bird1.png'},
        {textures: 'bird2.png'},
        {textures: 'bird3.png'},
      ], {
        duration: 300,
        direction: 'alternate',
        iterations: Infinity,
      })
      layer.appendChild(s)
    
      const util = {
        random(min, max) {
          return min + Math.floor(Math.random() * (max - min + 1))
        },
        randomColor() {
          return ['#22CAB3', '#90CABE', '#A6EFE8', '#C0E9ED', '#C0E9ED', '#DBD4B7', '#D4B879', '#ECCEB2', '#F2ADA6', '#FF7784'][util.random(0, 9)]
        },
      }
    
      const {Stage, Curve, motion} = curvejs
    
      const randomColor = util.randomColor,
        stage = new Stage(layer.canvas)
    
      stage.add(new Curve({
        points: [378, 123, 297, 97, 209, 174, 217, 258],
        color: randomColor(),
        motion: motion.rotate,
        data: Math.PI / 20,
      }))
    
      stage.add(new Curve({
        points: [378, 123, 385, 195, 293, 279, 217, 258],
        color: randomColor(),
        motion: motion.rotate,
        data: Math.PI / 20,
      }))
    
      function tick() {
        stage.update()
        layer.draw(false)
        requestAnimationFrame(tick)
      }
    
      tick()
    }())
    

    以上是SpriteJS的一些简单介绍。它还有许多神奇的功能,有兴趣的同学可以浏览SpriteJS的官方文档spritejs.org/

    要深入了解SpriteJS或者希望给SpriteJS贡献代码,可以关注我们的GitHub仓库。


    起源地下载网 » SpriteJS —— Canvas动画从未如此简单

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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