SpriteJS 是一款由 360 奇舞团开源的跨终端 canvas 绘图库, 可以基于 canvas 快速绘制结构化 UI, 动画和交互效果, 并发布到任何拥有 canvas 环境的平台上 (比如浏览器, 小程序和 node).
JS Bin on jsbin.com http://code.h5jun.com/dexol/1/embed?output
我们知道, Canvas Api 可以很灵活地绘制各种矢量图形到画布上, 但是 Canvas Api 本身比较低级, 比如我们要在画布中央绘制一个带有圆角的红色矩形, 使用 Canvas 原生的 Api, 需要这样:
- JSBIN http://code.h5jun.com/jobu/1/edit?js,output
- 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 https://code.h5jun.com/cajo?js,output
- 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 支持设置精灵元素常用的基本属性, 包括:
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 https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API
比如我们要让上面的圆角矩形的颜色从红色过度到绿色, 只需要:
- JSBIN https://code.h5jun.com/wivag/edit?js,output
- 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 https://code.h5jun.com/rari/edit?js,output
- 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 https://code.h5jun.com/vafu/edit?js,output
- 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 https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Animations_API 来实现更加复杂的动画, 对于 Web Animation API 或 CSS3 Animation 比较熟悉的同学应该对它比较熟悉:
- JSBIN https://code.h5jun.com/fidi/edit?js,output
- 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 https://code.h5jun.com/qevu/edit?js,output
- 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 https://code.h5jun.com/patej/edit?js,output
- 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 https://code.h5jun.com/logiv/edit?js,output
- 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 https://www.codeandweb.com/texturepacker 进行打包 (可使用 TP 的免费版, 导出配置文件格式为 JSON Hash)
例子 http://spritejs.org/demo/#basic_sprites
- (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 类型
例子 http://spritejs.org/#/zh-cn/elements?id=路径-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 https://developer.mozilla.org/en-US/docs/Web/SVG/Element/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 https://code.h5jun.com/wusod/edit?js,output
- 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 https://code.h5jun.com/wusod/edit?js,output
- 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 设置成一组, 进行统一的动画.
例子 http://spritejs.org/demo/#path_groups
- (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 代理给对应的精灵:
例子 http://spritejs.org/#/zh-cn/behavior?id=事件-event
- 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 :
例子 http://localhost:9090/#/zh-cn/guide/events?id=scene事件代理
- 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)
关于事件处理更多的内容, 可以查看 文档 http://localhost:9090/#/zh-cn/guide/events?id=事件和坐标
SpriteJS 和 D3
由于 SpriteJS 的 Api 和 DOM 拥有高度一致性, 因此它对 D3 友好, 可以方便地使用 D3+SpriteJS 实现数据可视化展现.
用 SpriteJS+D3 实现柱状图 http://spritejs.org/demo/#d3_bar
- (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 http://spritejs.org/#/zh-cn/examples?id=d3js
物理引擎
SpriteJS 的 扩展 https://github.com/spritejs/sprite-extend-matter 让它能够支持 https://github.com/liabru/matter-js
例子 http://spritejs.org/#/zh-cn/guide/matter
- // 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()
粒子系统
同样, 通过 扩展 https://github.com/spritejs/sprite-extend-proton ,SpriteJS 支持 Proton 粒子 https://github.com/a-jie/Proton
- JSBIN https://code.h5jun.com/xipi/edit?html,output
- 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 http://spritejs.org/#/zh-cn/examples?id=protonjs
外部时钟
SpriteJS 支持外部时钟, 这使得它可以很容易与其他效果库一起使用, 比如下面的例子演示了将 SpriteJS 与 AlloyTeam 的 Curvejs https://github.com/AlloyTeam/curvejs 一同使用:
例子 http://spritejs.org/#/zh-cn/guide/ticker
- ;(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 的官方文档 http://spritejs.org/
要深入了解 SpriteJS 或者希望给 SpriteJS 贡献代码, 可以关注我们的 GitHub 仓库 https://github.com/spritejs/spritejs . 对 SpriteJS 有疑问
来源: http://www.tuicool.com/articles/m6RJFr3