Git Product home page Git Product logo

canvas-tattle's People

Contributors

hongru avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

canvas-tattle's Issues

【Canvas杂谈:第一季】你别告诉我你只能贴矩形的图...

image

这一期,我们会讲点有趣的事情。
我相信会有同学感兴趣。

我们都知道,canvas的context2d中有一个drawImage的api。这是个很重要的api,尤其是做游戏类需要大量素材渲染的应用。

我们先不说drawImage这个api的参数可以有3个,有5个,也可以有9个。它的第一个参数可以是img,也可以是canvas,甚至可以值video。这些细节我们放到以后的某个话题来说。

我们今天的话题跟drawImage有关系,但有些特殊的地方。首先看下面的例子:
image
这是一张普通的图片,drawImage可以方便的把这张图画到canvas画布上。可是如果我想要的不是简单的规整矩形的图像,而是一个不规则四边形呢?比如这样。。。
image

不妨请各位同学想想,你看到这个需求后脑海里最直接的方案是什么呢??

好,我们今天的话题就是这个。。。它不是无稽的需求哦,在这个不规则4边形的基础上,我们可以做很多好玩儿的事情,这个后面再说 :) [吐舌头]

小编这里有一个
Demo
同学们可以点进去看一下,拉升4个角,看这张图片是以怎样的形态填充被你拉升的不规则4边形的。

接下来小编会说说自己的实现方案,“授人以鱼不如授人以渔”
这个实现跟还跟

  • transform
  • clip
    有关系。

我们先说说transform . 有关canvas的 transform ,6个参数释义如下:
image
每个参数单独看都很容易理解,但是当有不同的参数组合时,就蛮难有直观的想象了。详见这里

小编这里有个可以直观看到transform对绘制矩形影响的 Demo ,大概下面这个样子
image

接下来,同学们通过这个demo就大概可以看到,drawImage 结合 transform 就已经可以做到**“平行四边形”**的图片了。

注意是平行四边形 而不是我们最开始说的任意四边形

那么,怎么做到任意四边形的图片绘制呢....别忘了还有个clip的方法没有用上。


第二步,我们再看看clip有什么用?怎么用?
顾名思义,clip就是“裁剪”用的。

创建一个封闭路径,然后clip一下,之后再drawImage,fill之类的操作有效的绘制区域就只在刚才创建的那个路径中了。

可以参见W3School的解释Mozilla的一个Demo

其他的小编也不多说了,了解了transformclip这两个api,接下来就是见证奇迹的时刻
image


回到最开始的那个**Demo** 。
请把 debug 那个checkbox勾上,后面那个select 选择1 ,然后你就大概可以看到类似这样的表现
image

小伙伴们是不是明白了什么??

  • 每个四边形总可以切分成两个三角形
  • 所以我们把矩形的图片对角切分成两个三角形A和B
  • 把需要填充的那个不规则四边形也对角切分成两个三角形A' 和 B'
  • 接下来要做的就是把A画到A‘ 里面,B画到B’ 中

看下图:
image
其实不规则四边形 ABCE 可以由两个平行四边形 ABCEAFCE 叠起来,取各自的一部分组合起来。

那么,

  • 把矩形的图片通过drawImage 和transform 变成平行四边形 ABCE 的样子 没问题吧?
  • 再加上clip 只保留 ABC 这个三角形 没问题吧?
  • 同理,通过 clip + transform + drawImage 画出 三角形 ACE 也没问题吧?

如果以上的问题都有了答案,那么小编要说的方案也就差不多了。

当然,最后还有一个问题,如果你希望你的图片变不规则四边形的形变不那么厉害,有个简单的方法就是进行多次等比切分,将图片切分成一个个小矩形,不规则四边形也切分成等量的小四边形,对应着按上面的方式一个个填充就好了。


分割线之后,我们再来说说费了那么大劲,就搞个 矩形图片 -> 不规则四边形图片 ,这有什么价值?有什么应用场景?

小编为大家想到了一个很重要的场景:2d平面模拟3d效果的贴图

因为3D的效果有了z方向深度,投射到2d的平面上,就会变成类似梯形,甚至不规则四边形 的形状,这个时候如果想要贴图,如果你不用webgl,小编今天说的东西就有用了 :)

Demo 1
image

Demo 2:一个由**普通的FishEye的效果转换成的带图片的FishEye**
~~


写在后面的话:

  • 【Canvas杂谈】进行到第三期。小编一个人的力量一定是有限的。所以下期小编想找厉害的小伙伴来“撰稿”
  • 如果你有好玩又有价值的话题,一定不要放过它。
  • ATA:http://www.atatech.org/article/detail/11708/928

【Canvas杂谈:第一季】我只画了一条线,为什么我越来越慢?

image

本期【canvas杂谈】会开始说一些跟canvas确切相关的东西。本期的话题很小,但是却关系着使用canvas来做应用至关重要的东西:性能

比如,小编是一个刚刚接触canvas的开发者。想尝试用canvas的一些api完成一些demo,开始自己的学习旅程。
小编相信绝大部分刚开始接触canvas api的同学跟小编一样,最开始使用的通常是

  • rect
  • arc
  • moveTo
  • lineTo
  • fill
  • stroke
    这几个api,用它们来尝试在canvas画布上画点,线,圆...

于是,小编看了这几个api的使用文档,并且结合咋们【canvas杂谈】第一期的建议,用上了的requestAnimationFrame 做循环计数器,用上了大名鼎鼎的mrdoobstats.js
image

然后小编非常迅速的就写了一个画三角形的demo,小编非常得意且兴高采烈的观摩自己的**demo**:

可是,看着这个一动不动的三角形,不到半分钟,FPS嘭的一下从60帧掉到30帧,然后,后面还随着时间,不断的一截一截的往下掉。小编顿时就要哭了。。。
image

小编抓耳捞腮,想破了头也没想到哪里出了问题。于是,小编放弃了折腾这个问题,做下一个demo看看。上个demo是用fill方法填充了一个三角形,那这次就试试stroke,画一个带边的三角形。
于是,小编又有点小激动的完成了一个三角形的**Demo**

可怜的是,观察了不到一分钟,FPS又是刷刷的往下掉...

迫于无奈,小编只好回去查代码,一句句看。怎么也想不到,这么简单的几行代码,对性能有如此大的影响。

       function render () {
            ctx.moveTo(200, 100);
            ctx.lineTo(100, 250);
            ctx.lineTo(300, 250);
            ctx.lineTo(200, 100);

            ctx.stroke();
        }

最后,小编想到一个办法,把render函数里面的代码一句句删掉,看到底是哪句出了问题。于是首先把 ctx.stroke() 这句给注释掉。然后对着一个空白的画布,死死盯着FPS的变化。
。。。
。。。
一个小时之后,image
FPS始终保持在60,没有丝毫衰减的迹象。

那一刻,小编好像明白了什么。~~ “一定是 fill()stroke() 这样的api有严重的性能bug”


可是回过头一想,小编觉得自己被自己坑了,哪有这么多性能bug?? 人家这么多标榜性能的游戏谁没把canvas 的api用个遍啊,怎么能是说bug就bug的事儿呢。

回到我们的正题,简简单单几行代码,是什么原因造成的性能问题呢??
答案是绘制路径未封闭。于是从第一帧的moveTo 开始,这个路径就一直没有断过,一直牵到100帧,1000帧...

[修正:变慢不是因为路径没闭合 而是因为每次render都添加一个子路径 到最后越来越多就卡了
beginPath是把所有路径都清除 所以只加beginPath就不会变慢了]

等一下,我们回过头去看一下上面的几行代码,大概含义可以这么表述:

  1. 移动到第一个顶点(200, 100)
  2. 从这个顶点开始拉一个直线路径到第二个顶点(100, 250)
  3. 再从第二个顶点路径到第三个顶点(200, 250)
  4. 最后从第三个顶点回到开始的第一个顶点(200, 100)
  5. 最后是根据路径填充(fill)或者绘制路径(stroke)

小伙伴们从这个描述来看是不是觉得路径是封闭的。其实不然,canvas上判断这个路径有没有封闭,并不是路径最终能围起来就算封闭,而是需要有明确的指令告诉canvas,我这次的路径是从哪里开始的,到哪里就结束了。

那么,这个api指令不是别的。就是**beginPath()** 和 closePath()

function render () {
            ctx.beginPath();
            ctx.moveTo(200, 100);
            ctx.lineTo(100, 250);
            ctx.lineTo(300, 250);
            //ctx.lineTo(200, 100); 有了beginPath和closePath,即使不回到起点也不会出现性能问题
            ctx.closePath();

            ctx.fill();
        }

说到这里,恍然大悟的小伙伴们可能会说小编啰嗦了,说了这么一大堆,就是为了说**beginPathclosePath**的重要性。

木有错,知识点虽小,却能要了一个app的命。
image


写在后面的话:

  • 【Canvas杂谈】第二期,讲了一个很小却很重要的点。小编曾经因为这个小问题查bug差点查的断气... 最终发现是这个问题的时候又很想掌自己一巴掌有木有!!
  • 另,抛一个小问题,大家有没有发现。上面两个有bug的demo,跑起来FPS不是慢慢降低的,而是到某个时间点(大概一分钟左右)突然从60帧降到30帧左右,然后越来越慢,越来越慢。这个突变的根本原因是什么呢??
  • 最近大家都在忙双十一,双十二,一仗还没打完,一仗又开始了,所有双十一双十二的小伙伴们辛苦了!!
  • ATA : http://www.atatech.org/article/detail/11557/928

【Canvas杂谈:第一季 12期】关于imageData - 新年快乐

本期是Canvas杂谈第一季第12期。Canvas杂谈从到此按照每周一期的节奏。已经进行了3个月,也就是我们第一季到本期之后就结束了。
正好到年前我们走过了第一季。
年后回来我们继续我们的【Canvas杂谈:第二季】
至于第二季我们要讲什么,本期结束的时候给大家预告。

第一季最后一期,我们要讲什么?

如题所说,本期我们要说一个在canvas里面非常重要也算很有趣的一个点。作为我们第一季的压轴。就是imageData。

imageData是什么?

在canvas的context2d里面,有个getImageData的api

var ctx = canvas.getContext(‘2d’);
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); //表示取canvas的整个画布高宽的imageData

canvas这个元素,我们通常把他叫做画布。我们之前一期有讲过关于canvas像素这个东西。比如canvas.width=100 , canvas.height=100 。那么通常可以理解为这个canvas像素为10000。我们都知道,有种表达颜色的方式为rgba 。即一个像素的颜色用三原色加上透明度来表示。比如rgba(255,255,255,0.5)。表示透明度为0.5的白色。
那么,意味着一个像素颜色用rgba来算的话,有4个值。如果按照上面说的100x100的canvas有10000个像素点,每个像素点用rgba 4个值来表示。要描述这个canvas元素10000个像素点就刚好需要40000个数值。
简单的说,imageData就是这个40000个数值 组成的一个数组。

而且是“从左至右,自上而下”排列。

用imageData能做什么?

开始的时候小编说,利用imageData可以做很多有趣好玩的事情。小编下面举几个例子,给大家直观的感受一下。

自从双十一双十二之后,大家喜闻乐见的“刮刮卡”,有用到它

canvas杂谈之前有一期说到了关于clearRectdestination-out的应用,一个是标准的底层擦出canavs中一块矩形区域的api。合适的利用ctx.globalCompositeOperation = destination-out ,也能做到擦出的效果,而且还可以是不规则区域。
而怎么精确的算出不规则擦除区域到底有多大,就要用到imageData了。原理也可想而知,被擦除的区域在imageData这个数组里面的代表透明度的这个值是0 。所以对于一个有底色的刮刮卡,可以精确的知道透明度为0的像素点到底有多少。如此可以算的被擦除的区域占画布的比例。

另一个高大上的例子,图片滤镜

可想而知,我们可以把一张图片画到canvas上,又可以通过getImageData取到这个canvas中任意一个像素点的rgba值。那么我们可以对这个rgba值做任意的处理,然后再通过setImageData这个api,把处理后的色值再塞回去。由此可以做到图片像素处理的效果。
我们的 gallery 组件里面已经有了类似的组件,可以见http://gallery.kissyui.com/imageFilter/1.0/demo/index.html
腾讯也有个团队做过类似的事情。见 http://alloyteam.github.io/AlloyPhoto/ 功能和效果上更完整一些。

本质的实现原理都是如上所说,其他的顶多就是不同的图像处理算法的差异罢了。比如像素反转,高斯模糊等等。

新年祝福之一:抹抹涂涂出祝福【小编奉上】

newyear
可见【New Year Demo】
请使劲的涂涂抹抹,才能看到小编的祝福哦!

依旧是利用了imageData,取到了图片里面的像素点,按照一定比例做了放大和拆分。通过一些变形手段的处理,做了这么一个送祝福的Demo给大家玩乐。

新年祝福之二:粒子拼祝福【小编奉上】

xin nian kuai le

请见 【新年快乐 Demo】

关于imageData,应该注意些什么?

1)安全沙箱的限制,取不到跨域图片的imageData。
2)同样安全沙箱的限制,file://协议等本地化的协议下。取不到imageData。
3)如上所说,因为是rgba像素级的操作,即使100x100宽的canvas,从左上角到右下角取出整个canvas的imageData也是一个40000 长度的数组。可见操作一个imageData数组必然是个大数据量操作。性能的考虑也是一个非常重要的地方。

写在后面的话

  • 年关将近,【Canvas杂谈】第一季,完整的进行了12期,本着趣味,小而美的期望,每周一期。希望能给大家带来些趣味的同时也能带来的小知识。
  • 第一季到年前,刚好结束。我们年后【Canvas杂谈】第二季会继续开始。
  • 第二季的【Canvas杂谈】的主题会是一个连续性的主题,因为我们整个集团的前端委员会在进行一个“互动和游戏”共建的事情。关于Canvas在游戏方向的应用,以及怎么基于Canvas来构建游戏底层引擎。都会是我们第二季会进行的话题。
  • 【Canvas小组】和【互动游戏小组】会跟着集团内游戏引擎的进度,跟大家分享和讨论对于游戏引擎每一个feature的考虑和一些关键的实现。
  • 这么做的目的,一来是充分秉着“共建”的理念,公开透明,大家来了解,大家来讨论。二来也是希望能有更多对引擎构建有经验,有想法的同学进来有不同理念的碰撞。
  • 【Canvas杂谈:第一季】到此为止。完整的List,见ATA:
  • 【Canvas杂谈:第二季】我们年后见

【Canvas杂谈:第一季】啊啊啊!为什么我画不出1像素的线!!

本来这一期小编准备跟同学们邀稿的。已经有童鞋答应了,可惜大家都在忙双十一,双十二,看来要等这一阵忙碌的季节过去。小编要亲自去找感兴趣的童鞋们邀稿,稿酬暂订一超大杯starbucks。 :)
更欢迎自提issue来写。

image

这一期呢,小编跟大家分享一个“小技巧”。技巧虽小,解决困扰可不少。

正如话题所说。我们这次要说的话题是,怎么在canvas上画出1px的线条?


依旧,我们先看一个就几行代码的极简Demo, 请看这个 JSBin

就那么几行简单的代码,不知道大家会看出什么问题。
如题所说,我期望的是画出一条1px的线。但是从这个demo的表现来看,明显更像是2px的线。

问题出在哪儿呢?小编觉得,一定是lineWidth的原因,于是小编再设置一个 lineWidth=1,可以看这个**【Demo】**

于是发现其实设置了lineWidth=1 依旧没解决问题。所以,问题并不是出在lineWidth上面,其实默认的lineWidth就是1的。

那么,既然问题不是在lineWidth上,那会是在什么地方呢?

我们看另外一个**【Demo】**

这条线,确实就是1px的线了,那么这个demo和上一个有什么地方不同么?

对比一下,不同的就是终点位置不同,直观的来说,就是一条线的竖着的,一条是斜着的,竖着的短一点,斜着的长一点。

很明显,画不出1px的线的原因一定不是线段长度,那么就只能是 “斜”和“不斜”的原因了...

为了验证这个观点,我们用rotate的方式来制造"斜的线",一样可以达到预期的效果。比如这个 【Demo】


回到最开始的问题,如果不倾斜的线就没有办法做到1px了吗??

我们回想解决问题的思路,既然“斜的线”是对的,“直的线”不对,那我们把“斜的线”慢慢变成“直的”。找到那个发生问题的临界点,是否就能看出问题的本质了呢??

假设,让一条竖着的线旋转1度,会发生什么呢?看这个**【Demo】**
小编觉得,这条倾斜1度的线,基本能看到问题了。有木有发现,这条线被渲染的有点诡异,在某一段上,看得出是1px的线。但是其他地方像是显示器不够好,发生了“模糊”一样。

无疑,这并不是显示器的原因,比如你拿一个dom 的 transform 设置成 rotate(1deg) ,它的边依旧是清晰的。所以,canvas上直的1px的线发生模糊,只能归结到canvas本身的渲染机制的问题。

既然是canvas本身渲染的原因造成的困扰,该怎么绕过去呢?

说到这个问题,小编再把话题扯远一点。不知道大家有木有用过 dom 的 transform: translate(x, y) 来做动画。 我想,做mobile的同学使用这个属性的机会更大一些吧【偷笑】

使用transform:translate(x, y) 来做位置的动画,比直接操作 dom.styleleft,top 之类的属性来做动画,会平滑一些,在高级浏览器上性能也要好一些,但是有个问题是,当 translate(x, y)x 或者 y 不是整数的时候。 这个dom里面的内容的文字和图片等内容,都会发生“渲染模糊”。

当然,这个是浏览器本身渲染机制造成的问题,通常我们绕过去的办法是,如果使用translate(x, y)来做动画,那么在动画结束的时候,保证translate的值都是整数。 这样可以避免动画结束后 内容模糊的问题。

回到canvas上来,既然dom上的translate的值是“非整数”时会模糊,改成“整数”就好了,针对canvas上这个1px的模糊,是否可以通过位置的hack 可以绕过去呢?

我们看最开始的Demo,发现竖着的线是在 x 为50 的地方,这时是模糊的,那么假如我们将x的位置改成 “非整数”呢?

比如这个**【Demo】**

最后的结果,我想大家都看到了,两个端点的x位置 小编都改成了“非整数”,50.5开始的那一段已经是预期的结果了。
当然50.1的那一段还是模糊的。


我们最后总结一下:

  • 在canvas上,直的线,包括横的和竖着的,在画1px的线条的时候,会发生模糊现象
  • 倾斜的线条,不管是rotate出来的斜的,还是直接画出来的斜的,现象没那么明显,当然,如果倾斜角度太小,比如1deg,依旧会有这个问题
  • 画横着或者竖着的1px的线条,如果想要不模糊,请保证线条的位置在0.5的小数的位置。比如横着的线条,y的位置需要是***.5的小数,同理,横着的线条 x 的位置需要是 ***.5
  • 最后,除了0.5 的小数位置外,其他的小数达不到预期的效果。

【Canvas杂谈:第一季】我的身后,拖着一条长长的尾巴呀!

这应该是第... 7期了吧。小编有点记不太清了。
马上双十二就要到了。相信所有淘宝小二们,不管有没有具体的双十二的项目在手,都被这个紧张赶项目发布的气氛感染。忙的一塌糊涂。

小编也在此列,双十二发布在即。【Canvas杂谈】这一次,我们就说一个特别特别简单的东西。但是用到合适的地方,会有意想不到的效果。

先看这个【Demo】
2013-12-05 8 13 21

进入【Demo】之后,请用鼠标按住,采用类似“拖拽”的操作,mobile设备上用手指按住拖拽。可以发现点或者线段的运动变成了一条有着尾巴的小蝌蚪...
2013-12-05 8 27 08

小编不贴代码,因为这一期的重点不是这里面的算法是怎么算的,模拟是怎么做的。
这一期要讨论的,只是这个 “从点和线段转换成带尾巴的线的效果” 是怎么实现的。

它不是我们人肉算法实现的,仅仅是一个很小的小技巧。

fillColorrgba

如题所说,这就是用 rgbafillColor 是用的一个障眼法。
直接看这个简单的【JS-Bin】

2013-12-05 8 50 44


好了,本期到此为止。
其实要说的事情再简单不过...

ctx.fillStyle = 'rgba(***)' // 注:颜色无所谓,透明度是关键
ctx.fillRect(0, 0, canvas.width, canvas.height);
...
...

//最后,要注意这种透明度叠加的效果要循环绘制才会生效,不要clearRect

30行3D的Demo

一周一次的Canvas杂谈的时间又到了。
上周Canvas杂谈跟大家讨论了关于在Canvas画布上进行事件代理的实现方案和需要注意的各种问题。
因为重于理论,满满一篇的文字。
后来有同学反馈说全篇的文字看着头疼。
而且偏重理论的知识趣味性不高,又较难吸收,只有针对有这一部分需求的同学会有所帮助。
总而言之,就是不够轻松娱乐。
image


所以今天,我们来说个不实用,但轻松娱乐的事儿。换一换口味
如题,我们用30行js,写一个简单的3D的demo。

国外有个很出名的js社区活动,【JS1K】 要求很简单,就是使用1024字节以内的js,压缩后的,完成一个特效或者任意场景。然后由网友投票选出受欢迎的case。
随便点几个demo,然后知道这是1024字节以内的代码写出来的效果,估计都会露出以下表情
img

国内segmentfault社区搞过一个30js的活动,见http://segmentfault.com/q/1010000000340372 虽然30行js这个限制明显没有按字节1k来限制更公平,更有说服力。而且总体来说国内和国外的爱好者的鬼怪才能和能力也有差距。但总体来说还是个不错的活动。

理念就是“用更少的代码表现更丰富的内容”。

为了轻松娱乐,我们也来趟一次浑水,用大概30行代码做一个简单的3D变换的demo。

小编为了娱乐大众,昨晚上临时尝试了下。见这个 【Demo】
为了同学们阅读代码更容易一点,小编做了下代码的拆分,看起来会稍微容易一些。想看源码的童鞋可以右键。
小编这里就不贴了。
demo

更简化的版本在这里 【30line】

code

总共的代码大概如上图。

压缩字节的事儿我们就不多干了。最后还是说一说实现的关键部分.

关键点 rotateY

是的,重点只是在于demo中基于Y轴的3D旋转模拟是怎么做的?

见Demo源码中的 rotateY 的函数。
算法不是小编推导出来的,而是图形学中最基础的变换公式。如下
img

对照这个公式和 源码中 rotateY 函数的实现。童鞋们自己也可以实现一个类似的。而且,不仅仅是 绕 Y轴旋转,绕X轴,Z轴 的公式 本质上都是差不多的。

想要深入了解的童鞋 建议 google上 搜索 3d, rotate, 算法这几个关键词。 有不少相关资料...
img


本期Canvas杂谈到此为止,总结一下:

  1. 本期纯属娱乐,30行代码看看乐呵。
  2. 关于rotate3D的更多细节和实现。想深入了解的找小编私下讨论

【Canvas杂谈:第一季】RAF/FPS/dt干嘛用?

title


写在前面的话

  1. 为什么要写【Canvas杂谈】?

    答:还是抱着对于canvas的热情和态度,即使应用场景有限,不忍放下。加之目前集团淘宝,天猫,无线都有游戏化场景的需求。有一些和技术有关无关的话想和大家分享。
  2. 【Canvas杂谈】讲什么?

    答:和canvas有关的事情,他的好,他的坏,他坑爹的地方,以及可以和他做的窝心的事情。
  3. 该用什么样的心态来阅读【Canvas杂谈】?

    答:以“玩乐技术”的态度,它讲技术,但也讲故事。

我们今天要说的故事。要从RAF开始。
首先,RAF是什么?我相信大部分正在阅读此文的同学应该正在使用chrome浏览器。打开控制台,敲出webkitRequestAnimationFrame .
小编不会赘述这个东西干嘛用的,知道的同学已经知道了,不太了解的同学随便google一下也就知道了。说的简单一点,它就是个计时器 干着和setTimeout(callback, 16) 类似的事情。

为什么偏偏是setTimeout(callback, 16)
不知道大家对上面的16ms有什么疑义没?
image

两三年前当js动画的计时器都是使用setTimeoutsetInterval来实现的时候。小编曾经有过以下的蠢事。

  • 以前在做一些带动画的组件的时候,比如幻灯片slide,或者什么淡出淡出,划入划出的时候。总是觉得这个动画的时间间隔设的越短,动画表现就会越流畅。

所以曾经做过一件事:

setInterval(function(){  
    // animation func
}, 1);

最后发现其实setInterval 这种东西是有个最小的时间间隔的。比如小编现在在用的chrome里面 setInterval 的最小时间间隔就是4ms。

但是就算给他设置4ms的interval 它也不是4ms,因为... 我们还要考虑setInterval中的function的运行时间... 说到这里,剩下的小伙伴们自己去想吧。

好吧,回到开始的 16ms的话题。为什么16ms是合适的,而不是10ms或者20ms。当然这跟我们的人体工程有关系。有医学研究发现我们人眼对于刷新率的感知。每秒60帧几乎就满足了我们的眼睛。意思就是好像你花个5k块钱买个刷新率每秒200帧的电视看起来跟别人1k块的刷新率每秒60帧的电视看起来流畅度差不多... 汗!!
image

那么16ms 和60帧又有什么关系呢? 请打开计算器 计算一下, 1秒 = 1000毫秒, 1000/60 = ??

就算群众的眼睛是雪亮的..60帧也足够了。

16ms和RAF又有什么关系呢??
image
请看图,不解释 💯

然后,我们要说的是RAF和setInterval(callback, 16)以及setTimeout(callback, 16) 的关系
在扯之前,先让大家看两个demo 【图片可点】。
1-1 2-1

这两个实现的是同一个东西,虽然不是canvas实现的东西,但是跟RAF和setInterval有关。我相信大家用浏览器打开这两个demo看到的效果是一样的。当然,浏览器需要支持RAF,然后,请按下面的操作做:

同时打开这两个demo,然后浏览器最小化,或者切到别的tab去看看新闻啊,看看美女帅哥啊,过个半分一分钟再回到这两个页面来看看。
我想会突然好像知道了什么事情。。。

setInterval 和 setTimeout 在非当前窗口,或者浏览器‘休眠’的时候,即使渲染停止了,但是计时器不会休息,仍然会顽强的跑着!
这其实是一个不好的讯息。会大大加重浏览器的负担,同时也会影响这个页面或者app本身的性能。

以上,为第一个明显的不同的地方。

第二个不同的地方,在于callback队列的不同。小编举个栗子:

我们假设这里有3个循环计数器的实现,A是利用RAF的递归调用做的,B是用setInterval + 16ms做的,C是用setTimeout + 16ms + 递归调用实现的。
然后用这3个计步器来做一个callback的循环调用。同时假设这个callback执行消耗的时间固定为100ms。
那么我们可以大致猜想一下 B在做这件事情的具体过程:
B从时间零点开始第一次执行callback,16ms之后发现应该执行第二次了,但是发现第一次还没做完,于是还是得继续第一次没做完的事情,把第二次callback排到自己的一个计划队列里面,等到第一次执行完了再执行队列里的第二次...
但是恶性循环,32ms之后,第一次的事情还没做完,第3次的任务又来了。。。
于是,这个任务队列越来越长,越来越长。。。

C来安排任务的机制会比B稍微好一点点,但终究也逃不了B的厄运,身上的负担会越来越重。

我们来看A做这件事情的过程。

每一次任务的完成仍然需要100ms,但是浏览器不会在A没有完成当前任务的时候给它下一次的任务。也就是说A总是花100ms完成一次任务,然后再花100ms完成下一次任务。

所以,A完成10次任务所花的时间应该是1000ms,但是B和C完成10次任务应该需要>=1000ms,因为他们还要分心去管理他们的任务队列。

做个更形象的比喻:

  • 你做一个任务需要一天,但是你的老板每个小时都回来给你一个新任务。
  • 你做一个任务需要一天,等你做完今天的任务,你的老板会给你下一次的任务。

结果是你都花10天做完了10个任务,请问你觉得你更喜欢哪种方式??


RAF说完了,我们来说说FPSdt
我们都知道要让canvas里面的东西动起来,本质上你在位置1画了一个矩形,然后擦掉,然后在位置二画了一个同样的矩形。所以,这个矩形从位置一运动到了位置二。
我们把这个过程叫做“一帧”。

FPS: Frame Per Second, 翻译过来是每秒的帧数。
dt: Delta Time, 翻译过来是时间差,或者每帧的时间间隔。

理论上,fps和dt的关系就是 FPS = 1s/dt 。他们都是用来表示一个系统每秒能够运行的帧数的。简单来说,这两个东西可以反应动画的流畅程度。

那么,FPS通常是怎么获取到的呢?
我们看一个demo:

这是小编闲暇时用粒子的双密度松弛算法做的简单的流体模拟实验。我们来说说右上角的fps是怎么得到的呢?
根据 FPS= 1/dt. 所以首先我们先去拿到这个帧的时间间隔dt。在你的动画循环计数器开始的时候记录下来时间,和上一次记录下来的时间相减,就能简单得到一个当前帧的时间差dt。

代码就不多说,我想大家应该都知道怎么做。拿到dt之后,换算成fps,按合适的时间抛出来就可以了。

如果你觉得麻烦,推荐大家使用 mrdoob/stats.js

最后一个问题:dt 除了可以反应动画流畅度之外,还有什么用??
小编猜测,应该好多的同学没有意识到dt这个时间差在动画,或者游戏里面还有一个莫大的用处。
举一个场景:

假设我们在性能好的机器和性能差一些的机器上同时测试一个游戏。好的机器的FPS稳定在60,差的机器FPS稳定在30。
又假设我们给游戏主角的设定为“按住方向右键,主角在游戏场景里向右走动”。在这种设定下,假设A同学的实现为

//假设person为主角的一个实例,person.x 表示主角在游戏场景的位置。在每帧的update里如果这样写。
person.x += 1

那么在好的机器上,用户按下方向键1s,游戏执行了60帧,每帧人物的位置+1,那么人物移动了60的距离。
但是在坏的机器上,用户按下方向键1s,游戏只会执行30帧,同样每帧人物位置+1,人物只会移动30的距离。

这。。。显然不是我们期望的。

接下来,每帧的时间差dt就派上用场了。如果我们在每帧的update里面这样写:

person.x += dt*60;

好的机器上,用户按下方向键1s,游戏执行60帧,每帧的间隔dt是1/60, dt_60 就是1,每帧人物移动1的距离。1s移动60的距离。
坏的机器上,游戏执行30帧,dt=1/30, dt_60=2; 也就是每帧人物会移动2的距离,那么1s下来,人物移动总距离还是60.

说到这里,小编觉得应该忽然明白了什么。。。
image


本期的【Canvas杂谈】到此为止,总结一下:

  • 如果你在做动画,不管是不是基于canvas的,不妨尝试一下requestAnimationFrame 。至于浏览器的兼容问题,小编认为你自有方法搞定。
  • 如果你在做动画,不妨把FPS 暴露出来,对性能的检测和优化一定有用的。
  • 如果你在做动画,帧的时间差dt 可以发挥更大的用处。

【完】


写在后面的话,

  • 这里是【Canvas杂谈】第一期,文字有点多,看官们不要厌烦。
  • 【Canvas杂谈】以季度为单位,每周一期,希望能对你有所帮助。 第一季话题草案 issues
  • 知之为知之,不知为不知,如果叙述有不准确,欢迎来 issues 里讨论。
  • 如果您有想听的话题,请来**New Issue** 里提。小编会有规律的随机选取有价值的话题撰文。
  • 每期【Canvas杂谈】会同步更新到ATA canvas小组圈子 http://www.atatech.org/gprofile/928

【Canvas杂谈:第一季】我有菱角,请让我变得圆滑

在游戏化的场景里,其实有很多需要折线变圆滑曲线的的需求。说到这里,相信很多同学一定第一时间想到的是贝塞尔曲线。而且在canvas底层的api里,其实也提供了绘制“二次贝塞尔曲线” 和 “三次贝塞尔曲线” 的方法。
quadraticCurveTo()bezierCurveTo()

没错,我们今天要说这两个api,但是本篇的重点却不在它们那儿。


quadraticCurveTo()

二次贝塞尔曲线,构成有3个点,一个起始点,一个控制点,一个结束点。通常使用方式:

ctx.beginPath();
ctx.moveTo(20,20);
ctx.quadraticCurveTo(20,100,200,20);
ctx.stroke();

比如上面的代码意思就是

“画笔”移动到[20, 20]的位置,以[20, 100]为控制点,[200, 20]为终点绘制一条二次贝塞尔曲线。

详细可见【w3schools的Demo】 ,我就不多说了。

bezierCurveTo

三次贝塞尔曲线,一个起始点,两个控制点,一个结束点。4个点构成。简要的使用方式如下:

ctx.beginPath();
ctx.moveTo(20,20);
ctx.bezierCurveTo(20,100,200,100,200,20);
ctx.stroke();

上面代码的大致意思是:

"画笔"移动到[20, 20]的位置,然后以[20, 100], [200, 100]这两个点为控制点,最后以[200, 20]为结束点,绘制一条3次贝塞尔曲线。

依旧详细可见【W3Schools的Demo】


开头就说过了,本篇的重点不是单纯的讲这两个api。回到我们的主题:

怎么利用canvas提供的这两个贝塞尔曲线的api,将一条随机的折线,拟合成一条平滑的曲线??

在小编说自己的解决方案之前,大家如果有空可以先想一下。在一条折线上,开始点,结束点,控制点改怎么取,才能很好的利用已知的二次和三次贝塞尔曲线的api,拟合出没有拐点,平滑的曲线??

请先拿出纸笔先大概画一画,就会发现,要想没有拐点的拟合出平滑的曲线,怎么样取点?没点讲究是做不到的。

小编这里会和大家分享自己的实现方案。有两个,一个是使用贝塞尔曲线的,一个是不使用贝塞尔曲线的方式。

使用二次贝塞尔曲线来拟合任意折线

【Demo】
bezier
如上图所示,一条顶点大于等于3个的折线,最终要拟合成一条平滑的曲线。怎么选取控制点,选用二次曲线还是三次贝塞尔曲线?这是个问题。

我相信凭借大家的聪明才智,一眼就可以看破了天机。

上面一条随机折线 ABCDEF ,最终拟合成那条红色的曲线。看图就能够知道是怎么做到的:

折线组成中的5条线段 AB, BC, CD, DE, EF, 除去最开始和最后的线段,其余的线段分别取它们的中点,得到C0, C1, C3 三个点。

  1. 以A为起始点,C0为结束点,B为控制点,利用quadraticCurveTo做一条二次曲线.
  2. 以C0为起始点,C1为结束点,C为控制点,再做一条二次曲线
  3. 以此类推,直到完成C2-E-F

当然,上面的做法是假设折线由n条线段组成,取第2条到n-1条线段的中点,然后进行二次曲线拟合。其实也可以不取中点,取线段上其他比例的点也可以,最终结果只是平滑度的问题。

比如我们取1/3 比例的点。通常为了对称,就需要多取一个点,也就是一条线段这一端的1/3取个点,另一端的1/3也取个点。这时候,我们再每3个点进行二次曲线的拟合。
3
看起来就会是这个样子,这个时候除了只是把折线的菱角磨成圆滑之外,每条线段就会有一段仍然是直的。

这种情况算不算拟合成功呢... 其实在实际的应用中,这种情况通常也是满足要求的,关键是看你的需求是什么样的。

假如不用贝塞尔曲线,该怎么做呢...

既然叫拟合,就代表是“无限趋近”的意思。那么我们按这个思路,让一条折线无限趋近于曲线,有什么做法呢?

小编这里给大家介绍一个更直接的折线插值的办法。怎么做呢? 先看这个**【Demo】**

小编依旧截个图跟大家简要说明是怎么回事,其实原理很简单,一说就明:
2013-11-22 10 20 31

比如上图折线ABCDEF, 从B点开始,距离顶点1/3 的长度位置 分别取一个点,比如B点向两边各取1/3 得到C1, C2 点。 C点向两边各1/3 得到C3, C4 点。以此类推,得到一条新的折线 A-C1-C2-C3-C4-C5-C6-C7-C8-F 。拿到这条新的折线再重复上面的做法。
重复3~4次,你就会发现,折线从视觉上看,已经慢慢平滑了起来。

我们把这个方式,叫它 Chaikin Curve - 插值曲线。它实际上不是真正的曲线,而是折线模拟。


  • 本期【Canvas杂谈】就到此为止。按照惯例,小编依旧尽量只讲思路,不贴代码,需要代码的同学可自行右键 :)
  • 本期介绍了两个api quadraticCurveTobezierCurveTo
  • 着重说明了两种折线拟合曲线的思路,一种是用二次曲线,一种是用折线插值
  • 但是这两种方式都只是在符合折线的方向趋势上来平滑过度,除了首尾两点之外,中间的点都没经过。如果我们要拟合出一条经过折线所有顶点的平滑曲线,有可能么?改怎么做? 有思路的同学给小编信哦,非常期待。

【Canvas杂谈:第一季】我是scale,中文名叫缩放,但我不是只会缩放.. 囧

双十二刚刚结束,所有刚刚奋战完双十二的小二们刚从一波工作,一波项目中释放出来,马上又会面临下一波的的工作。

咋们的【Canvas杂谈】本着小而美的方向继续。
本期如题所说,要讨论的内容跟scale有关。

关于context2dscale是怎么回事

要说scale相关的东西,我们首先应该知道scale是怎么回事,怎么个用法。
小编这里依旧不长篇赘述scale的基本用法,因为有地方会比我说的更好。
比如【W3School】的讲解和示例

小编这只简单截个图:
screen shot 2013-12-13 at 2 43 31 pm

screen shot 2013-12-13 at 2 42 15 pm

以下正题,关于scale的三条事儿

看过【canvas杂谈】前几期的童鞋们应该都知道,我们不纠结与底层api的基本用法,而是着重于和这个主题有关的“小而美”的技巧。以及实用的一些解决方案。

所以,接下来,小编会和大家讨论和scale有关的重要且有趣的三件事。

【第一】scale不仅会缩放大小,也会缩放位置。

这是这个api明确说明的一点,但是也是经常会被大家遗忘和忽略的一点。
在上面【W3School 的demo】上也可以明确看到这一点。

一个同样大小,相同位置的矩形,在scale(2, 2)前后绘制出来的表现是,不仅高宽被放大的两倍, x,y两个方向的位置也被放大了两倍。同时lineWidth也是被放大的两倍。

【第二】scale除了缩放,还能做别的事...

以上,第一点算是比较基本的api使用的注意事项。但是其实scale其实可以巧用来干别的事儿。
【这个JS-BIN】
img
左边是原图,右边是canvas绘制的一张横向翻转的图。

小编所说的scale做的别的事情,这个就算一项。当然,这并不是canvas的scale特有的东西,css3 的 scale 也有这个巧用。
screen shot 2013-12-13 at 4 08 04 pm

所以:使用scale 的时候,别忘了 scale的值 可以是 负数哦

【第三】没有transform-origin,我该怎么办

首先,我们先说说transform-origin。这个在css3 的 transform 中非常有用。用处就是用来设置rotatescale 等transform变换的中心点。

  • CSS3中的 scale 和rotate 的变换,默认的变换中心点为当前元素的中心。等效于默认设置transform-origin:50% 50%。这样可以保证scale和rotate的默认变换以元素中心为参考点。
  • 而且每个不同的dom元素可以单独设置transform-origin.
  • canvas的context2dscalerotate 的默认参考点是哪呢? 是canvas元素的左上角。而且没有类似transform-origin的东西可以更改他transform的参考点。
我们怎么来做到类似CSS3 中transform-origin呢??

见【这个JS-Bin】 。中文有可能乱码,重要代码见下图:

img

代码翻译过来就是:

  • 先用translate将画布绘制起点移到当前绘制区域的中心
  • 然后进行scale,rotate等操作
  • 最后进行绘制图形的时候,绘制的位置考虑上这个区域 中心 到 左上角 的距离。

如此而已。

至于怎么样让每次变换只针对当前区域或者元素有效,这个就要借助save()restore() 了。 这个我们以后再说。


好,本期【Canvas杂谈】到此为止。总结一下

  • canvas的scale和css3的scale类似,值可以为负数,用负数可以做到 “反转” 等 效果
  • canvas上的scale 缩放 不仅影响视觉大小,也会影响视觉位置 和其他相关状态
  • 利用translate 改变绘制位置的参考中心的小技巧,可以做到类似transform-origin 的控制。

【Canvas杂谈:第一季】我在上面画了一个圈,你能帮我长出事件么?

前言

这次我们要讨论的话题,从基础上说,是一个简单的话题,但是想要把它做周到,做全面,做深入,实际上是需要很多的工作,考虑很多的细节的。

这一次我们要说的话题就是有关于事件在Canvas上虚拟元素上的派发

最基本的情况

我们来说一个场景

有一个Canvas画布,画了一个矩形上去,现在要做的事情是,类似给这个矩形绑上事件,点击这个矩形内的区域就alert('click') 。矩形外的区域点击无响应。

那么该怎么做这件事情呢?
<canvas>也是个dom元素,它有dom原生的事件系统,这个没问题,但是在Canvas画布内部画上去的东西,没有独立的事件系统。只能依托于画布的事件来做这件事。也就是要判断鼠标点击的位置。

思路很直接:

  • 获取到鼠标点击的位置相对于<canvas>画布的相对位置P0。
  • 拿到画的这个矩形相对于画布的相对位置
  • 最后直接判断点击点击的点是否落到这个矩形内就ok了。最终问题简化成点是否落在矩形内的问题。

复杂一些的情况

如果有多个矩形在画布内,可能相互都有交叠,每个矩形点击都应该出现不同的东西。当点的位置发现有多个矩形叠在一起的时候,我们期望的情况是只认为“鼠标位置的视觉最上层”的那个矩形点击有效。其他的都不应该触发。

怎么处理这种情况?
如果直接按照和上面类似的"面向过程"的方式来处理这件事情,会相当的困难。甚至在代码里都不能很好的区分哪个事件属于哪个矩形...
所以无疑,我们必定要引入在Canvas内“面向对象”的编码方式。在Flash AS 和传统DOM 的结构里,都默认集成了基础的对象系统。
AS里面有DisplayObject,
DOM里面可以直接操作HtmlElement
但是Canvas里面没有... 这需要我们自己做。
自己做“对象系统”,自己做“事件系统”。

常见的思路:

  • 所有绘制出来的东西,不管是个矩形也好,图片也好,文字也好,都包装成一个对象,定义为sprite,或者layer
  • 有了这个基础对象模型,在这个对象模型上做事件系统
  • 假如我们已经做好了针对单个“sprite”对象绑定事件的功能。现在面对多个“sprite”对象绘制在一块画布上,并且可能会有重叠的情况,那么我们要处理的情况是要找出满足**“包含鼠标点击位置并且在视觉的最上层”**的那个矩形。
  • 包含鼠标位置的所有矩形,这个容易拿到,遍历一遍对象就可以。
  • 在视觉表现的最上层,在不设置特殊的globalCompositeOperation的情况下,其实就是最后绘制的元素。
  • 那么最终在代码层面的逻辑简化为 “找到包含当前鼠标点击位置的所有矩形中的 最后绘制的那一个”

mouseovermouseout的特殊处理

我们上面举得例子都是用的 点击click在举例。它的派发还相对容易些。而mouseovermouseout 这种事件在 sprite这种虚拟元素上的 派发,会有其他的问题,我们来分析下场景:

  • 所有虚拟对象sprite的事件派发,本质上都要经过<canvas>这个DomElement的事件来做,比如我在一个矩形的spriteA上绑定一个click事件,当满足触发条件的时候,触发事件回调 FuncA 。本质上是在<canvas>这个tag上绑定了一个'click' 事件,然后回调函数里面 做好了 spriteA 是否被点中 的判断逻辑, 再确定是否执行 spriteA 的回调 FuncA。
  • 但是mouseovermouseout 不能用这种方式来处理。
  • 因为<canvas>mouseovermouseout事件,只会在鼠标进入Canvas元素 和 离开Canvas元素的时候 触发一次,所以即使给 sprite 绑定了 mouseover 或者 mouseout 的事件,鼠标在<canvas> 元素里再怎么动,也不会再触发mouseovermouseout
  • 那么怎么处理这种情况呢?该想到也应该想到了,在mousemove里面做处理...

理理思路:

  • canvas元素的mousemove事件中,分析上一次move触发时鼠标位置下的元素列表ListA 和 当前move触发的 鼠标位置下的元素列表ListB。
  • 假设ListA中 有 [SpriteA, SpriteB], ListB中有[SpriteA, SpriteB, SpriteC] 。那么就表示SpriteC是当前这次move刚进入的元素。
  • 而且刚好SpriteC满足**“是鼠标位置下层叠元素上最后绘制的那一个”** 这个条件。那么表示SpriteC 的 mouseover 或者 mouseenter 触发条件满足。
  • mouseout 的触发判定也类似...

如果不是规整矩形,而是不规则多边形的sprite

上面我们说的情况都是基于规则矩形的情况来说的。如果不是一个规则的矩形,而是一个不规则的封闭多边形 或者 圆形。 这种情况下 sprite 的事件分发又该怎么处理呢?

  • 其实本质上和上面说的规则的四边形的思路一致
  • 只不过需要把 “鼠标是否落在矩形内” 的检测 变成 “鼠标是否落在这个不规则封闭多边形内”
  • 然后重复上面的各种情况测试

还要考虑存在scalerotate 的情况!!

事情还没有结束,上面所说的情况还不涉及到 存在 缩放旋转的情况。

假设一个矩形的spriteA 以 它的中心为 旋转点 旋转了45度,那么这个时候鼠标 的位置 是否落在这个矩形中,判断中要变成“点是否落在一个旋转45度的矩形”中的问题。
同样,如果带缩放的元素做事件派发的判断。同样要判断的是 点是否落在 缩放变换后的矩形中。

解决方案:

  • 在同一变换纬度内做判断。
  • transform变换最终可以转换成一个6维matrix矩阵,矩形的缩放或者旋转,其实都可以用矩阵变换来代替,那么,如果一个元素做了某种矩阵变换,为了保证判断的鼠标坐标 和 这个元素的变换纬度统一。可以让 鼠标这个点 也进行同样的矩阵变换。
  • 然后在同样的变换纬度内重复上面讨论过的判断流程。

scalerotate需要考虑的点

scalerotate 一旦介入我们事件派发的考虑范畴。问题瞬间变复杂很多。

比如以下:

  • scale受影响的因素:
    • css中的transform:scale
    • canvas的context2d.scale()
    • canvas 的 style:widthstyle:heightcanvas.width ,canvas.height 的比对.

比如

<canvas width="1000" height="600" style="width:500px; height:300px"></canvas>

等效于做了scale(0.5, 0.5) 的变换。

  • rotate受影响的因素:
    • css中的 transform:scale
    • canvas 的context2d.rotate

所以,如果想要完整Fix scalerotate 以及其他transform变换对于事件派发的影响,上述的因素都必须考虑进去。

最后,请考虑整个事件派发系统是采用“捕获”的方式还是“冒泡”的方式。

上面说了这么多,最后放一个**【Demo】**
这个demo也只实现了上述说的issue中的一部分。


本期的【Canvas杂谈】到此为止。
全篇基本都是文字,没有代码,没有图片,大部分同学我想都对这种纯文字的东西不感冒,没耐心完整细看下去。
但是我相信以上说的 关于 在 一个画布里面 做虚拟元素的面向对象封装,加上事件派发的系统。如果你想做这件事,那么上面的东西或多或少都会有用。
希望能对有这方面需求的同学一点帮助。

【Canvas杂谈:第一季】她叫Retina,怎么才能配得上漂亮的她?

写在前面的话

这期是canvas杂谈的第11期,第一季的canvas杂谈快接近尾声了,我们按一周一期的频率。一个月4期,一季就是12期。按照季度为单位分别讲关于canvas不同的一些事情。可大可小。
第一季结束之后,不用担心【canvas杂谈】就此完结,我们第二季的主题大致已经定下来。先卖个关子,等到第一季最后一期的时候再跟大家说我们第二季会说什么事情。

本期的话题从题目可以看到,关于Retina的适配的问题。

最近不少童鞋都跟小编我有讨论到这个话题。因为现在Retina屏越来越多,好多同学在实际应用中也确实遇到了这个问题,Retina的适配开始变成了一种“刚需”,在浏览型的web上是如此,在以canvas为基础构建的app上亦是如此。

canvas实际像素和可视像素的区别

在进入主题之前,我们先说说关于canvas的像素的东西。随便开一个关于canvas的demo,比如【W3School的一个demo】
看看他的canvas标签的内容
screen shot 2014-01-04 at 11 34 26 am
会发现,大部分跟canvas有关的demo,在初始化canvas的高宽的时候,都是直接用的标签属性Attributes 而不是像大多数dom元素一样,去定义StyleRules里面的 widthheight

那么,假设我们不去定义canvas的 Attributes里面的 widthheight ,而是定义StyleRules里面的widthheight ,会有什么问题呢?

依旧拿【W3School的一个demo】 这个demo做实验。把

<canvas id="myCanvas" width="250" height="300" style="border:1px solid #d3d3d3;">

这个改成如下

<canvas id="myCanvas" style="width:250px;height:300px;border:1px solid #d3d3d3;">

结果是什么样子呢?? 如下:
img img

<canvas>这个dom元素的大小依旧是 250x300 .但是画布当中绘制的内容却差了很远... 原因是什么呢?

Attributes中的width和height 跟 StyleRules中的width 和 height 不是一个东西

我们定义的canvas的width和height,

<canvas width="250" height="300"></canvas>

或者

//js
canvas.width = 250;
canvas.height = 300;

上面的方式定义的都是canvas的实际像素的高宽。

<canvas style="width:250px; height:300px"></canvas>
//js
canvas.style.width = '250px';
canvas.style.height = '30px';

这种方式定义的是样式的高宽,也就是视觉的高宽。

如果这样依旧不好理解。那么直观的说:

<canvas width="200" height="200" style="width:100px;height:50px"></canvas>

上面的代码的表现其实本质上是 把 一个 200x200 的canvas画布 强制缩放成100x50 大小来显示,注意是缩放而不是裁剪。

canvas的默认size是多少?

先看之前提到的一个问题,假设我不设置 canvas 元素的 width 和 height,而只是针对StyleRules 的width 和height做了设置,问题是什么?

上面说过,如果同时设置了 AttributesStyleRules 中的width 和 height,等同于把 Attributes中定义的大小 缩放成 StyleRules 中定义的大小来显示。

那么,不设置Attributes的size,就涉及到canvas画布在dom中默认的大小是多少?
见下图:
img

也就是说至少在chrome和safari中的表现是,canvas元素默认的size是300x150。

回到上面的问题

<canvas style="width:250px; height:300px"></canvas>

只定义StyleRules的width和height,如上,等效于 把一个 300x150 的canvas 缩放成 250x300 来显示。

不做Retina适配的问题是什么?

这个问题很简单。

  • 因为canvas不是矢量的,而是像图片一样是位图模式的。
  • 那么如果不做Retina屏幕的适配的话,所有按照1:1的大小绘制到canvas元素上的东西,放到Retina屏上都会和1:1的图片一样,看起来是模糊的。
  • 包括在canvas上绘制的文字。

在canvas上适配Retina的方式?

正如上面所说,既然理解了canvas的显示的模式,而且跟图片有一定的相似之处。那么按照img在Retina上的适配思路,不难想到:

  • img在Retina屏上的适配方案是,用@2x 两倍大小的图,然后给img设置1倍的高宽,进行缩放,如果是背景图就用background-size 进行压缩成1倍来显示
  • 那么,canvas适配也是类似的思路。利用上面说的 AttributesStyleRules 中的width 和 height 的设置,来进行类似的将2倍大小的canvas 压缩成1倍大小来显示。就可以让Retina屏幕上的canvas绘制的图像清晰。

具体的举例:

比如针对iphone5 来说,全屏的视觉大小是 320x568 ,但是是Retina屏的。假设我们要做一个完整全屏的canvas应用,那么实际的做法是

<canvas width="640" height="1136" style="width:320px; height:568px"></canvas>

然后在canvas内部的操作和代码,所有位置,大小,也都按 640x1136 来做。
这是目前最简单也最常用的方法。

需要注意的问题...

有些同学为了偷懒,在非Retina屏上的canvas也用上面2倍压1倍的做法,这样做本质上没有致命性的问题。但会有下面3个问题。

  1. 非Retina屏上也用两倍压1倍的做法,必然导致资源的浪费,毕竟@2x的图片比1倍的图片光从体积上来讲,大的也不是一点点。
  2. 比较严重的一点是部分Android2.2, 2.3 等低版本老机器,是不支持canvas的像素压缩的。也就是说,即使按照上面的方式把Canvas的Attributes的 width 和 height 设置为2倍, StyleRules 中的width 和 height 设置为1倍,在这一部分低版本的老机器上的表现是被截掉了,而不是被压缩。
  3. 在性能上也有不小的影响。小编之前就说过,canvas上所有绘图api的性能消耗都跟 canvas元素 的大小是正比例关系的。重绘区域越大,性能越低。

img

img

所以,最后的建议依旧是Retina和非Retina屏幕分开处理,Retina上用@2x的canvas绘制@2x的图片和文字,非Retina的设备依旧按1:1来做。

这样对于低版本机器的适配,对于性能,都有好处。


本期【Canvas杂谈】到此为止。

【Canvas杂谈:第一季】我是画笔,我也可以是橡皮擦

本期,是【Canvas杂谈】第一季的第六期。
前面几期我们都是在讨论怎么样去画一个东西,比如画一些线条,画一些图片。以及处理一些特殊效果的小技巧。

今天我们就不卖关子了,我们不讲要怎么画一个东西,而是怎么擦掉一些东西,而且是有节操,有限制的擦掉一些东西。


我相信,说到在canvas上擦掉一块东西,略熟悉一点canvas的童鞋第一反应 应该是clearRect这个api。
没错,clearRect确实就是用来帮助我们擦掉画布的一块或者全部的区域,便于我们下一帧的重绘。

如果还不了解clearRect这个api的童鞋,请见【W3School 的释义和demo】 小编这里就不细说了。
2013-11-29 11 23 45

假设小编要做这样一件事情:

//先把画布填满黑色
//然后我在[50, 50]的位置填充一个200x200的红色矩形
//然后我想擦掉红色区域的左上角50x50的区域

在JS-Bin 里我们简单写几行代码来做上面的事情。比如这个
2013-11-29 1 54 26

接下来,我们有一个新的需求,就是我想要擦掉的地方,不是一个矩形,而是一个圆或者多边形或者不规则图形,该怎么办呢??

小编告诉大家有一个在canvas里面可能不常用到的属性叫做globalCompositeOperation, 它有个值叫做 destination-out,可以做到类似的事情。如下:
2013-11-29 2 02 06

有木有发现,即使不用clearRect,也做到了“擦除”类似的效果。

而且,进一步会发现,在将canvas的globalCompositeOperation 设置为destination-out 之后,所有之前表现为"画上去"的操作都变成了"擦除"的效果。

fill()是如此,stroke()亦是如此。


假设我们做这样一件事情:

  • 给canvas弄张背景图
  • 然后在canvas上填充灰色的颜色
  • 再将ctx的globalCompositeOperation 设置为destination-out
  • 之后再stroke()一些粗线条

于是乎,我们看到了这样的效果:
2013-11-29 2 20 30

到此为止,大家是不是明白了什么??


当然,globalCompositeOperation 这个属性可以做的事情还有很多。
看看【Mozilla 的这个Demo】
或许大家会清晰很多。


最后要说一点,是擦除的效果上面基本已经把原理说清。那么怎么计算被擦除的部分占画布区域的百分比大小呢?
如果去取每个线段的长度和宽度来计算,就不得不考虑重叠的部分和圆角的部分...
难度大且不准确。

所以,有一个更精确且直接的方法是使用imageData, 小编这里先卖个关子,不细讲。等到后面说到跟imageData相关的事情的时候会再说。

因为imageData可以做的事情实在太多了!!!


本期【canvas杂谈】到此为止。总结一下:

  • 擦除的效果除了clearRect还有globalCompositeOperation
  • globalCompositeOperation 可以做一些好玩的事情

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.