打造高大上的Canvas粒子动画,HTML也可以静态编译

打造高大上的Canvas粒子动画

2016/08/22 · HTML5 · 5
评论 ·
Canvas

原文出处:
腾讯ISUX   

首先来看下我们准备要做的粒子动画效果是怎么样的~

是这样:

图片 1

或者是这样:

图片 2

甚至是这样:

图片 3

很酷炫!

那如何去实现类似上面的粒子动画甚至根据自己的喜好去做更多其他轨迹的动画呢~请看下面详细的讲解。

HTML也可以静态编译?

2016/11/30 · HTML5 · 1
评论 ·
binding.scala,
React,
前端

本文作者: 伯乐在线 –
ThoughtWorks
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

More than React系列文章:

《More than
React(一)为什么ReactJS不适合复杂的前端项目?》

《More than
React(二)React.Component损害了复用性?》

《More than React(三)虚拟DOM已死?》

《More than
React(四)HTML也可以静态编译?》


《More than
React》系列的上一篇文章《虚拟DOM已死?》比较了Binding.scala和其他框架的渲染机制。本篇文章中将介绍Binding.scala中的XHTML语法。

三看 SVG Web 动效

2016/11/30 · HTML5 · 1
评论 ·
SVG

原文出处:
凹凸实验室   

图片 4

CSS3 动效玩腻了吗?没关系的,我们还有 SVG。

Welikesmall
是一个互联网品牌宣传代理,这是我见过的最喜欢使用 SVG
做动效的网页设计团队。事实上,越来越多的网页动效达人选择在 SVG
的疆土上开辟动效的土壤,即便 SMIL 寿将终寝,事实上这反而将 SVG
动效推向了一个新的世界:CSS3 Animation + SVG。

图片 5

(SMIL is dead! Long live SMIL! A Guide to Alternatives to SMIL
Features)

还记得我在久远的《以电影之眼看 CSS3
动画》中说道:“CSS3
动画简直拥有了整个世界!”那么带上 SVG 的 CSS3
动画则已突破天际向着宇宙级的可能性前进(感觉给自己挖了一个无比巨大的坑,网页动画界可不敢再出新技术了[扶额])。

CSS 与 SVG 的打通无疑将 html 代码的可读性又推上一个台阶,我们可以通过
CSS 控制 SVG
图形的尺寸、填色、边框色、过渡、移动变幻等相当实用的各种属性,除此之外,将图形分解的动画在这种条件下也变得相当简单。

其他前端框架的问题

索引

本文将讲到三个动效例子:

  • 箭头描线动效
  • 播放按钮滤镜动效
  • 虚线描线动效

动效来源:WLS-Adobe

即将聊到的 SVG 标签:

  • <path>
  • <g>
  • <symbol>
  • <defs>
  • <use>
  • <clipPath>
  • <mask>

以及属性:

  • viewBox
  • preserveAspectRatio
  • fill
  • stroke
  • stroke-dasharray
  • stroke-dashoffset
  • d
  • clip-path
  • mask

技术选择

因为粒子数量很多,而且涉及到图像像素处理,所以这里使用Canvas是不二选择。

 

注意,以下演示的代码只是关键代码,重点在于解决思路。

对HTML的残缺支持

以前我们使用其他前端框架,比如Cycle.js
、Widok、ScalaTags时,由于框架不支持
HTML语法,前端工程师被迫浪费大量时间,手动把HTML改写成代码,然后慢慢调试。

就算是支持HTML语法的框架,比如ReactJS,支持状况也很残缺不全。

比如,在ReactJS中,你不能这样写:

JavaScript

class BrokenReactComponent extends React.Component { render() { return (
<ol> <li class=”unsupported-class”>不支持 class
属性</li> <li style=”background-color: red”>不支持 style
属性</li> <li> <input type=”checkbox”
id=”unsupported-for”/> <label for=”unsupported-for”>不支持 for
属性</label> </li> </ol> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BrokenReactComponent extends React.Component {
  render() {
    return (
      <ol>
        <li class="unsupported-class">不支持 class 属性</li>
        <li style="background-color: red">不支持 style 属性</li>
        <li>
          <input type="checkbox" id="unsupported-for"/>
          <label for="unsupported-for">不支持 for 属性</label>
        </li>
      </ol>
    );
  }
}

前端工程师必须手动把 classfor 属性替换成 className
htmlFor,还要把内联的 style
样式从CSS语法改成JSON语法,代码才能运行:

JavaScript

class WorkaroundReactComponent extends React.Component { render() {
return ( <ol> <li className=”workaround-class”>被迫把 class
改成 className</li> <li style={{ backgroundColor: “red”
}}>被迫把样式表改成 JSON</li> <li> <input
type=”checkbox” id=”workaround-for”/> <label
htmlFor=”workaround-for”>被迫把 for 改成 htmlFor</label>
</li> </ol> ); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class WorkaroundReactComponent extends React.Component {
  render() {
    return (
      <ol>
        <li className="workaround-class">被迫把 class 改成 className</li>
        <li style={{ backgroundColor: "red" }}>被迫把样式表改成 JSON</li>
        <li>
          <input type="checkbox" id="workaround-for"/>
          <label htmlFor="workaround-for">被迫把 for 改成 htmlFor</label>
        </li>
      </ol>
    );
  }
}

这种开发方式下,前端工程师虽然可以把HTML原型复制粘贴到代码中,但还需要大量改造才能实际运行。比Cycle.js、Widok或者ScalaTags省不了太多事。

从一个简单的例子说起

图片 6

要做出这样的效果,第一步是将图形画出来。徒手敲代码这种事还是留给图形工具来做,但是,为了更好地控制与制作动效,咱至少要做到读懂
SVG 代码。

SVG 的基本格式是使用 <svg> 标签对代码进行包裹,可直接将代码段插入 html
中,也可以保存成 svg 文件之后使用 imgobject 进行引用。

XHTML

<svg width=”100%” height=”100%”> <!– SVG markup here. –>
</svg>

1
2
3
<svg width="100%" height="100%">
<!– SVG markup here. –>
</svg>

由于交互动效所需,这里仅介绍直接使用 svg 标签的情况。

XHTML

<svg width=”90″ height=”13″ viewBox=”0 0 89.4 12.4″> <line
x1=”0″ y1=”6.2″ x2=”59.6″ y2=”6.2″></line> <line x1=”54.7″
y1=”0.7″ x2=”60.5″ y2=”6.5″></line> <line x1=”54.7″
y1=”11.7″ x2=”60.5″ y2=”5.8″></line> </svg>

1
2
3
4
5
<svg width="90" height="13" viewBox="0 0 89.4 12.4">
<line x1="0" y1="6.2" x2="59.6" y2="6.2"></line>
<line x1="54.7" y1="0.7" x2="60.5" y2="6.5"></line>
<line x1="54.7" y1="11.7" x2="60.5" y2="5.8"></line>
</svg>

这是箭头的代码段,使用了最简单的线条进行绘制。可以看到其中包裹了许多坐标样的属性值。有坐标就意味着有坐标系。

SVG
的坐标系存在三个概念:视窗、视窗坐标系、用户坐标系。视窗坐标系与用户坐标系属于
SVG 的两种坐标系统,默认情况下这两个坐标系的点是一一对应的。与 web
其他坐标系相同,原点位于视窗的左上角,x 轴水平向右,y 轴垂直向下。

图片 7

(图片来源:MDN-SVG
Tutorial-Positions)

SVG 的位置、大小与文档流中的块级元素相同,都可由 CSS 进行控制。

视窗即为在页面中 SVG 设定的尺寸可见部分,默认情况下 SVG 超出隐藏。

SVG 能通过 viewBox 属性就完成图形的位移与缩放。

viewBox属性值的格式为(x0,y0,u_width,u_height),每个值之间用逗号或者空格隔开,它们共同确定了视窗显示的区域:视窗左上角坐标设为(x0,y0)、视窗的宽设为
u_width,高为 u_height;这个变换对整个视窗都起作用。

下图展示了当 viewBox 尺寸与 SVG 尺寸相同、放大一倍、缩小一倍时的表现:

图片 8

图片 9

图片 10

一句话总结,就是用户坐标系需要以某种方式铺满整个视窗。默认的方式是以最短边为准铺满,也就是类似
background-size 中的 cover 值。通过 preserveAspectRatio
属性你可以控制用户坐标系的展开方式与位置,完美满足你的各种需求。

preserveAspectRatio
是一個以對齊為主,然後再選擇要自動填滿還是咖掉的屬性。——引用来源《SVG
研究之路 (23) – 理解 viewport 與
viewbox》

属性的语法如下:preserveAspectRatio="[defer] <align> [<meetOrSlice>]"

注意3个参数之间需要使用空格隔开。

defer:可选参数,只对 image 元素有效,如果 image 元素中
preserveAspectRatio 属性的值以 defer 开头,则意味着 image
元素使用引用图片的缩放比例,如果被引用的图片没有缩放比例,则忽略
defer。所有其他的元素都忽略这个字符串。

meetOrSlice:可选参数,可以去下列值:

  • meet – 默认值,统一缩放图形,让图形全部显示在 viewport 中。
  • slice – 统一缩放图形,让图形充满 viewport,超出的部分被剪裁掉。

——引用来源《突袭 HTML5 之 SVG 2D 入门6 –
坐标与变换》

align:必选参数。由两个名词组成。

這兩個名詞分別代表 viewbox 與 viewport 的 x 方向對齊模式,以及 y
方向的對齊模式,換句話說,可以想成:「水平置中 +
垂直靠上對齊」的這種感覺,不過在這個 align
的表現手法倒是很抽象,可以用下方的表格看出端倪:

图片 11

也因此我們要做一個「水平置中 + 垂直靠上對齊」的 viewbox
設定,就必須寫成:xMidYMin,做一個「水平靠右對齊 + 垂直靠下對齊」的
viewbox
設定,就必須寫成:xMaxYMax,不過這裡有個細節請特別注意,「Y」是大寫呀!真是不知道為什麼會這樣設計,我想或許跟命名規則有關吧!

——引用来源《SVG 研究之路 (23) – 理解 viewport 與
viewbox》

下图诠释了各种填充的效果:

图片 12

(图片来源:7 Coordinate Systems, Transformations and
Units)

在这一层面处理好图形的展示之后,剩下的所有变换,无论是 translate、rotate
还是 opacity,我们都可以全权交给 CSS
来处理,并且可以将图形细化到形状或者路径的层面进行变换。

然而实际情况是,刚才的那段代码,放进codepen之后是什么也看不见的,原因就在于这个路径的绘制既没有填充颜色也没有描边。

一、绘制粒子轮廓图

首先要在canvas画布上绘制一个由粒子组成的轮廓图,记录下每一个粒子的坐标,这样才能有后续的动画。

不兼容原生DOM操作

此外,ReactJS等一些前端框架,会生成虚拟DOM。虚拟DOM无法兼容浏览器原生的DOM
API
,导致和jQuery、D3等其他库协作时困难重重。比如ReactJS更新DOM对象时常常会破坏掉jQuery控件。

Reddit很多人讨论了这个问题。他们没有办法,只能弃用jQuery。我司的某客户在用了ReactJS后也被迫用ReactJS重写了大量jQeury控件。

填充——fill

fill 属性用于给形状填充颜色。

CSS

svg line { fill: #000; /* 填充黑色 */ }

1
2
3
svg line {
fill: #000; /* 填充黑色 */
}

填充色的透明度通过 fill-opacity 设置。

fill-rule 用于设置填充方式,算法较为抽象,除了 inherit
这个取值,还可取以下两种值:

nonzero:这个值采用的算法是:从需要判定的点向任意方向发射线,然后计算图形与线段交点的处的走向;计算结果从0开始,每有一个交点处的线段是从左到右的,就加1;每有一个交点处的线段是从右到左的,就减1;这样计算完所有交点后,如果这个计算的结果不等于0,则该点在图形内,需要填充;如果该值等于0,则在图形外,不需要填充。看下面的示例:

图片 13

evenodd:这个值采用的算法是:从需要判定的点向任意方向发射线,然后计算图形与线段交点的个数,个数为奇数则改点在图形内,需要填充;个数为偶数则点在图形外,不需要填充。看下图的示例:

图片 14

——引用来源《突袭 HTML5 之 SVG 2D 入门4 –
笔画与填充》

然而我们发现,我们的箭头即使填充了颜色,还是什么也看不见,问题就出在我们绘制的时候使用了没有面积的
line 标签。这个时候,就需要出动描边了。

1. 创建一个<canvas>元素,并获取Canvas画布渲染上下文

图片 15

<
canvas>是一个双标签元素,通过width和height的值来设置画布的大小。至于ctx(画布渲染上下文),可以理解为画布上的画笔,我们可以通过画笔在画布上随心所欲的绘制图案。如果浏览器不支持canvas会直接显示<canvas>标签中间自己设定的文字。当然<canvas>标签中间也可以是一张当不支持canvas时需要替换显示的图片。

 

2. 使用canvas的图像操作API绘制图像

绘制图像的关键API及参数说明:

图片 16

引用MDN上的一张图会比较清晰的看出每个参数的作用:

图片 17

drawImage就是把一个image对象或者canvas上(甚至是video对象上的的每一帧)指定位置和尺寸的图像绘制到当前的画布上。而在我们的需求中,是要把整个图像绘制到画布中。

图片 18

对应浏览器看到的效果:

图片 19

 

Binding.scala中的XHTML

现在有了Binding.scala ,可以在@dom方法中,直接编写XHTML。比如:

JavaScript

@dom def introductionDiv = { <div style=”font-size:0.8em”>
<h3>Binding.scala的优点</h3> <ul>
<li>简单</li> <li>概念少<br/>功能多</li>
</ul> </div> }

1
2
3
4
5
6
7
8
9
@dom def introductionDiv = {
  <div style="font-size:0.8em">
    <h3>Binding.scala的优点</h3>
    <ul>
      <li>简单</li>
      <li>概念少<br/>功能多</li>
    </ul>
  </div>
}

以上代码会被编译,直接创建真实的DOM对象,而没有虚拟DOM。

Binding.scala对浏览器原生DOM的支持很好,你可以在这些DOM对象上调用DOM
API,与 D3、jQuery等其他库交互也完全没有问题。

ReactJS对XHTML语法的残缺不全。相比之下,Binding.scala支持完整的XHTML语法,前端工程师可以直接把设计好的HTML原型复制粘贴到代码中,整个网站就可以运行了。

描边——stroke

这个 stroke 可得大书特书,因为光是这个 stroke
就能搞定80%的描线动效。

直接通过 stroke 设置描边色,我们就能立刻看到刚才的箭头了。通过
stroke-width 则可以对描边的粗细进行修改。

CSS

svg line { stroke: #000; stroke-width: 1px; }

1
2
3
4
svg line {
stroke: #000;
stroke-width: 1px;
}

图片 20

3. 获取图像的像素信息,并根据像素信息重新绘制出粒子效果轮廓图

canvas有一个叫getImageData的接口,通过该接口可以获取到画布上指定位置的全部像素的数据:

图片 21

把获取的imageData输出到控制台可以看到,imageData包含三个属性:

图片 22

其中,width、height是读取图像像素信息完整区域的宽度和高度,data是一个Uint8ClampedArray类型的一维数组,包含了整个图片区域里每个像素点的RGBA的整型数据。这里必须要理解这个数组所保存像素信息的排序规则,请看下图描述的data数组:

图片 23

每一个色值占据data数组索引的一个位置,一个像素有个4个值(R、G、B、A)占据数组的4个索引位置。根据数列规则可以知道,要获取第n个位置(n从1开始)的R、G、B像素信息就是:Rn
= (n-1)*4 ,Gn = (n-1)*4+1 ,Bn = (n-1)*4+2  ,so easy~
 当然,实际上图像是一个包括image.height行,image.width列像素的矩形而不是单纯的一行到结束的,这个n值在矩形中要计算下:

图片 24

由于一个像素是带有4个索引值(rgba)的,所以拿到图像中第i行第j列的R、G、B、A像素信息就是
Rij = [(j-1)*imageData.width + (i-1)]*4 ,Gij =
[(j-1)*imageData.width + (i-1)]*4 + 1,Bij =
[(j-1)*imageData.width + (i-1)]*4 + 2,Aij =
[(j-1)*imageData.width + (i-1)]*4 + 3 。每个像素值都可以拿到了!

接下来就要把图像的粒子化轮廓图画出来了。那么,怎么做这个轮廓图呢,我们先读取每个像素的信息(用到上面的计算公式),如果这个像素的色值符合要求,就保存起来,用于绘制在画布上。另外,既然是做成粒子的效果,我们只需要把像素粒子保存一部分,展示在画布上。

具体做法是,设定每一行和每一列要显示的粒子数,分别是cols和rows,一个粒子代表一个单元格,那么每个单元格的的宽高就是imageWidth/cols和imageHeight/rows,然后循环的判断每个单元格的第一个像素是否满足像素值的条件,如果满足了,就把这个单元格的坐标保存到数组里,用作后续绘制图案用。

关键参考代码:

图片 25

用完整代码做出的demo及效果:

图片 26

至此,粒子轮廓图已经制作完成。

 

Binding.scala中XHTML的类型

@dom方法中XHTML对象的类型是Node的派生类。

比如,<div></div>
的类型就是HTMLDivElement,而
<button></button> 的类型就是
HTMLButtonElement。

此外, @dom
注解会修改整个方法的返回值,包装成一个Binding。

JavaScript

@dom def typedButton: Binding[HTMLButtonElement] = {
<button>按钮</button> }

1
2
3
@dom def typedButton: Binding[HTMLButtonElement] = {
  <button>按钮</button>
}

注意typedButton是个原生的HTMLButtonElement,所以可以直接对它调用 DOM
API。比如:

JavaScript

@dom val autoPrintln: Binding[Unit] = {
println(typedButton.bind.innerHTML) // 在控制台中打印按钮内部的 HTML }
autoPrintln.watch()

1
2
3
4
@dom val autoPrintln: Binding[Unit] = {
  println(typedButton.bind.innerHTML) // 在控制台中打印按钮内部的 HTML
}
autoPrintln.watch()

这段代码中,typedButton.bind.innerHTML 调用了 DOM API
HTMLButtonElement.innerHTML。通过autoPrintln.watch(),每当按钮发生更新,autoPrintln中的代码就会执行一次。

线的虚实:stroke-dasharray

(敲黑板)王牌属性出现辣!
这个属性的属性值是1到 n 个数字,多个数字由逗号隔开,CSS
中的定义则由空格分开,每个数字定义了实线段的长度,分别是按照绘制、不绘制这个顺序循环下去。

下面是设置了1个、2个、3个数字时虚线的描绘情况对比:

图片 27

发表评论

电子邮件地址不会被公开。 必填项已用*标注