我用canvas简单折线图做了一个简单的涂鸦板,遇到一个问题,求助

canvas图片问题浅析 - 简书
canvas图片问题浅析
Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这是我写canvas图片业务遇到的两个问题。
正文较长,结论在最后。
本文适合正在接触canvas图片业务的前端同学;当然,没接触过的,提前看看有哪些坑也是极好的:D
欢迎读完后打脸(比如说哪些地方没说明白啦,哪些地方存在知识点问题啦)!
?先简单说下跟本文相关的需求:涂鸦板里能嵌图片;能把图片导出;由于有多张图,为了让体验更好还需要有个预加载方案。
Paste_Image.png
写demo的时候我用的本地图片,调canvas toDataURL方法并没有报错。
但是在联调的时候,换成外域图片,却报错了:
Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
按惯例去stackoverflow上查了查,找到了解决方案(详情可以看):
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
当时没想那么多,加进去试试再说,不出意料地解决了问题,不禁再次感叹so大法好!
然而在加了图片预加载代码之后,发现有的图片就加载不出来了,打开控制台报错:
Paste_Image.png
开始以为是图片服务器那边没有设CORS,联系那边说设了;然后说「你们怎么用的源站域名,源站的域名可能导致种种问题,改用CDN域名试试」,但发现还是有问题。然后逐步定位到是图片预加载代码的问题,改了之后似乎?就好了。
好景不长,后来由于?QA哥哥的一个「误操作」,又出现了同样的问题,我的内心是崩溃的。。
上面简单地说了下我遇到问题与解决问题(赶进度)的过程,接下来要入坑辣~
先说说 Tainted canvases may not be exported 的问题。对于外域图片,?浏览器仍然是允许你画到canvas上的,但是toDataURL就会报错(toBlob也是)。为什么会这样呢?
This protects users from having private data exposed by using images to pull information from remote web sites without permission.
上面这段引用?摘抄自。在对应的语境里,大意就是说:如果你请求外域的图片without permission,可能会暴露你的隐私数据,所以浏览器为了保护你的隐私会限制这样的请求。
「wtf?请求外域图片怎么就会暴露我的隐私数据了??」其实我也不明白,这个坑请先自己填一下,之后会补充。
那么怎么绕过浏览器的「关照」呢?答案是?:你允许就行了~而img.setAttribute('crossOrigin', 'anonymous');就是告诉浏览器,我允许?!
再说说'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.。
这个报错的根源是:
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
// 外域url
(这个异常实际上在控制台里是拿不到调用栈的,浏览器并不会告诉你是这里出了问题)
这个异常信息本身是说「reponse header中不带Access-Control-Allow-Origin(以下简称AC)这个字段,所以'xxx'被同源策略阻止了?」。
(如果你想进一步了解同源策略,可以看看阮老师的。)
这时候你可能会想起,我之前不加img.setAttribute('crossOrigin', 'anonymous');,也去请求外域图片,怎么就没报过错?
这里我简单补充一下?:img.setAttribute('crossOrigin', 'anonymous');,加了这句,就意味着你这次的图片请求变成了CORS请求,就要受同源策略的限制了(而这个报错就说明你受到了浏览器同学的关怀:D)。
其实因果关系是这样的:img.setAttribute('crossOrigin', 'anonymous');会让request header加上Origin字段,从而变成了一个CORS请求:
Paste_Image.png
(如果你想进一步了解CORS,可以看看阮老师的。)
回到正题,既然问题是response header中不带AC,那让服务端返回应该就可以了吧?
如果服务端真的没有配置CORS,那先让他们配置好。
但是?,即使配置了?,仍然可能存在?问题。
在我遇到的情况里,其实服务端是做了配置的,那谁来背锅?
==================== 缓存 ====================
首先,第一锅要给浏览器缓存。
这里先赘述一下:我们第一次访问一个页面时,会发现图片会慢慢加载出来;当我们再次访问同一个页面时,会发现图片很快就加载出来了。主要就是因为浏览器第一次已经把图片缓存下来了,第二次不需要再从服务端请求,而直接从缓存里取。
虽然方便了,但这可能引发其它问题。上面提到过,原先的图片预加载代码有问题,简化版如下:
for(var i in images){
img = new Image();
img.src = images[i].
注意,这段代码没带img.setAttribute('crossOrigin', 'anonymous');。其实本质上并不是因为没带这句才出的问题,跟实际的场景有关。
当时的场景是:图片预加载先行;然后编译第一个涂鸦板,之后选中其它的涂鸦板再编译该涂鸦板;每个涂鸦板编译的时候也会去发送图片请求(CORS请求)。
问题的现象是:第一个涂鸦板的图片加载出来了,后面几个都没加载出来。
对于第一张图片,两个请求(来自预加载和涂鸦板编译)几乎是同时发送的;而其它几张图片,都是预加载在先,编译在后。如此,在编译其它几个涂鸦板时,浏览器会直接取缓存里取图片。
而我们预加载时发送的是普通请求,这意味着这些请求的response不会带AC(不是必然的,取决于服务端怎么做):
普通请求.png
CORS请求.png
所以,当其它涂鸦板编译时,发出的是CORS请求,拿到的却是不带AC的response,结果必然出错。
这里我得再强调一下,并不是普通请求的response就一定不带AC,这个取决于服务端怎么处理。比如像请求七牛公共空间的图片,不管是普通请求还是CORS请求,都会带AC。
知道原理之后解决问题就简单了,先清清缓存,然后加上crossOrigin:
for(var i in images){
img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = images[i].
So,到此为止?No,我们有请第二位背锅先生:CDN缓存!
上面提到过,我们的图片域名由源站改为了CDN。
先还原一下当时的场景:
有一位老师用涂鸦板批改作业,当她保存的时候发现保存不了(这是另一个无关的问题,不赘述),就请QA哥哥帮忙。QA哥哥打开控制台......(省略一万字),然后在一个新tab里打开了一张图片。当他再回到原页面时,一刷新,发现这张图片没了。当时我就跪地上了。。。
我是束手无策了,于是找了CDN的gg们帮忙。他们说的确存在这种问题,正在修复中。。
在进一步讲之前,结合我的手残图,先普及几个CDN相关的知识:
Paste_Image.png
CDN会缓存response,源站不会。
CDN接收到请求时,如果没有缓存,会将请求发送到源站,将结果回传给请求端,并且缓存结果(response),简称回源。
CDN是根据url进行缓存的,比如你请求一次http://a.b.c/1.jpg,之后再请求相同的url,那你拿到的是缓存下来的response;如果你加了个参数比如http://a.b.c/1.jpg?100,这个时候就会回源,但是并不会破坏掉http://a.b.c/1.jpg对应的缓存。
以上3点只是我们这边的情况,也许有特殊性。
现在可以简单理理,这是个怎样的问题:
老师的图片本来?是可以加载到的,并且在没「打开图片」之前,都是发送的CORS请求(在涂鸦板预加载和编译时发送),这些CORS请求的response早已在A节点缓存了下来。
而打开这张图片,意味着一次普通请求,奇怪的是,请求去到了B节点,而B节点尚未缓存,所以进行了回源。
而刷新页面后,请求虽然是CORS请求,但是却又走到了B节点,结果就是:一个CORS请求?拿到一个普通请求的response,浏览器由于同源策略而报错。
(正常情况下,如果一开始去到A节点,那么应该一直都是去A节点。)
嗯,道理明白了。那除了等gg们修复问题,还有什么解决办法吗?
我猜你已经想到了:加随机数。
最终的做法是在图片onerror的时候带随机数(比如时间戳)重发请求,大概就是:
function requestImg(src){
var img = new Image();
img.onerror = function(){
var timeStamp = +new Date();
requestImg(src+'?'+timeStamp);
总得来说,当你遇到这两个问题的时候,需要做两件事:
img.setAttribute('crossOrigin', 'anonymous');
图片请求失败时,带随机数重发请求。
善始者实繁,克终者盖寡。
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
0. 前言 前面有被用户投诉 APP 流量消耗厉害: 于是乎考虑了流量方面的问题。暂时 APP 中涉及流量的几个方面: 普通 https 请求,wzp 请求文本传输,请求已经做了 gzip,流量占比小 配置文件下载,应用内升级下载包触发的时机较少,每次升级版本才触发。另外整...
发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注
09:45字数 61697阅读 3316评论 2喜欢 85 用到的组件 1、通过CocoaPods安装 项目名称 项目信息 AFNetworking 网络请求组件 FM...
大年三十已经过了,在外奔波的人们大多已回到了故乡。当他们坐上了返程的列车上,虽然起始站不同,但终点都是家,都是那个让自己魂牵梦萦的家,那个对自己日夜牵挂的家。 我仍然身处千里之外的异乡,这个城市虽说未限于空城的境况,但看到空寥寥的街道,稀疏的人群,心里不免空落落的。 之前总...
一直以来都以为自己看人挺准。一直在做着和人密切相关的工作,在学校时是班主任,在企业也是做人力资源管理工作。最基础的工作就是招聘,有时候遇到咨询项目,受客户委托招聘,初试、面试、笔试,层层把关,所用到的工具也是专业的和民间的共用,比如人才测评、比如九型性格、比如星座,近几年老...
林宝航坐在饭桌前时,显得有点腼腆,就像一个大男孩。他若有所思地看看我,好奇地问一句:“那,你妈妈爸爸在哪里工作?”我们这个年龄,对陌生人——虽然面前的林宝航已经认识半天了,都有点羞忸。我扭着身子,歪着脑袋,还鼓起腮帮子,嘴巴里噗噗噗地吹着响声,借以转移分散我的小小扭捏。可就...
东锅技校原属国家大型企业——东方锅炉厂下属的专业技术工人培训学校,是原国家劳动部首批批准的自贡市唯 一的一所重点技校,是集教学、培训、技能鉴定、就业安置、生产经营、产品开发为一体的实体企业,现有教职工120人,在校学生1800人。 建校以来,学校本着“诚信做人,严谨办学”的...sketchpad-简单的HTML5 Canvas涂鸦画板插件
当前位置: >
> sketchpad-简单的HTML5 Canvas涂鸦画板插件
sketchpad是一款基于HTML5 Canvas的涂鸦画板jQuery插件。它可以使用鼠标在画板上绘制各种涂鸦图案,可以修改线条的尺寸和颜色,还可以回播绘制的过程动画,以及将绘制的图案保持为Json或对象,在其它地方进行恢复操作。
浏览器兼容性
小编推荐:
sketchpad是一款基于 的涂鸦画板。它可以使用鼠标在画板上绘制各种涂鸦图案,可以修改线条的尺寸和颜色,还可以回播绘制的过程动画,以及将绘制的图案保持为Json或对象,在其它地方进行恢复操作。
可以通过npm或bower来安装该涂鸦画板插件。
npm install sketchpad
$ bower install sketchpad --save
使用该涂鸦画板插件需要引入sketchpad.css,jQuery和sketchpad.js文件。
&link rel="stylesheet" href="css/sketchpad.css"&
&script src="js/jquery.min.js"&&/script&
&script src="js/sketchpad.js"&&/script&
该涂鸦画板的HTML结构使用一个&canvas&元素来制作。
&canvas class="sketchpad" id="sketchpad"&&canvas&
初始化插件
你可以通过new Sketchpad()来实例化一个涂鸦画板实例。
var sketchpad = new Sketchpad({
element: '#sketchpad',
width: 400,
height: 400,
在得到涂鸦画板对象的引用之后,可以使用下面的方法来操作涂鸦画板。
sketchpad.undo();
sketchpad.redo();
// 修改颜色
sketchpad.color = '#FF0000';
// 修改线条尺寸
sketchpad.penSize = 10;
//回播绘制的线条动画(每条线之间间隔10毫秒)
sketchpad.animate(10);
sketchpad涂鸦画板插件的github地址为:
本文版权属于jQuery之家,转载请注明出处:
您已经顶过了哦!使用Canvas绘制旋转动画
使用Canvas绘制旋转动画。在使用Canvas绘制旋转动画时我们会遇到一个问题:使用context.rotate();是将画布进行旋转,而并不是画布上的绘制的图形,那我们要怎样做才能使画布上图形旋转呢?
假设现在已经写好了页面,定义了Canvas元素,下边列出js代码:
1.创建画布:
var context=Elem.getContext('2d');//Elem为接收到的Canvas元素
2.获取画布的宽高
const WIDTH=Elem.
const HEIGHT=Elem.
3.加载要绘制的图片:
var img=new Image();
img.src=&xxx.png&;
4.平移画布:
context.translate(WIDTH/2,HEIGHT/2);
5.绘制图片
img.onload=function(){
context.drawImage(img,-img.width/2,-img.height/2);
//到此,静态图片效果已经出来了
6.旋转图片
context.rotate(Math.PI);
context.drawImage(img,-img.width/2,-img.height/2);//旋转完进行再次绘制
这已经完成了一个旋转动作,如果要连续旋转,只需在加上定时器与时间控制即可。一个关于使用html5的canvas标签的问题_百度知道
一个关于使用html5的canvas标签的问题
最近使用html5的canvas遇到一个坐标不统一的问题,如上图,在使用Chrome的手机浏览器模式时,虚线(C-&A)是鼠标划过的路线,实线(D-&B)是在canvas上显示的实际路线,就是说纵坐标不一样,横坐标不变;但是当我切换回电脑的普通浏览器模式的时候就不会出现上述...
我有更好的答案
最好写一个坐标转换的函数,把全局坐标转换成canvas内部的坐标,并且这块儿可以单独拿出来测试,而你画线是从canvas的左上角开始的。因为很明显你的鼠标实际的轨迹是从页面的左上角开始的你我怀疑你是直接用页面中鼠标的坐标值在canvas里边画线的,发现问题也好改
但是我使用普通浏览器模式运行的时候就没问题的
采纳率:64%
为您推荐:
其他类似问题
html5的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。指尖的流畅体验--基于canvas建立移动界面3 years ago无效层当一个层需要重绘,例如一个图像加载后,它发送一个信号到绘图引擎表示其框架是脏( dirty )的。这些修改使用 requestAnimationFrame来批量处理,避免,之后在下一帧画布重绘。达到60fps的滚动对于 web 端,也许我们最理所当然关注的,是浏览器如何来滚动网页。浏览器厂商已经提高滚动性能。这其实是一个妥协。为了达到60 fps 的滚动指标,移动浏览器在执行滚动期间,停止 JavaScript 的执行,这是怕 DOM 修改导致回流。最近, IOS 和 Android 暴露了 onScroll 事件,他们的工作过程更像桌面浏览器了,但如果你试图在滚动时保持 DOM 元素的位置同步,具体的实现可能会有差别。幸运的是,浏览器厂商已经意识到这个问题。特别是 Chrome 团队已经了为了改善手机端这种情况所做的工作。回到 canvas ,简短的回答是你必须用 JavaScript 实现滚动。你首先需要的是一种计算滚动程度的算法。如果你不想研究数学,Zynga 开源的,适合任何类似此布局的情况。我们使用一个 canvas 元素来完成滚动。在每一个触摸事件时,根据当前的滚动程度去更新渲染树。之后,整个渲染树使用新的坐标来重新渲染。这听起来令人难以置信的慢,在 canvas 上可以使用一个重要的优化技术–画布上绘图操作的结果可以在离屏层(off-screen)canvas 被缓存。离屏层(off-screen)之后可以用来重新绘制层。这种技术不仅可以用于图像层,文本和图形也适用。两个成本最高的绘图操作是填充文本和图像。但是一旦这些层绘制一次以后,接下来使用离屏层重新绘制他们是非常快的。(注意,这里图片比较大,知乎无法上传,大家可以访问 )在上面的演示中,每个页面的内容分为两层:图像层和文本层。文本层包含多个元素组合在一起。每一帧滚动动画中,这两层都使用位图缓存来重绘。   对象池在一个无限列表的滚动过程中,大量的 RenderLayers 会被建立和销毁。这会在内存中创建大量的垃圾,当进行垃圾收集时将停止主线程。为了避免产生大量垃圾, RenderLayers 与相关对象汇集到一个池中。这意味着只有相对较少的层对象被创建。当不再需要时,它会被释放回池中,之后可以重用。   极速快照缓存复合层的特性可以带来另一个优势:能够将渲染的部分结构作为一个位图。你有没有建立部分 DOM 结构快照的需求?当你将这些结构渲染在 canvas 时,速度会快得令人难以置信。 这个将一个项目放入一本杂志的界面,利用了这种特性来执行一个时间轴维度的平稳过渡。快照包含去掉顶部和底部的整个项目。(知乎的gif图显示貌似有问题,大家可以访问这个链接看下面这张图)一个声明式的 API现在我们已经拥有了构建一个应用程序的砖块。然而,通过命令来构建 RenderLayers 可能是乏味的。如果我们有个类似于DOM工作方式的声明式 API 不是很好么? React我们是 React 框架的忠实粉丝。它的单一定向的数据流和声明式API已经改变了人们构建应用程序的方式。react最引人注目的特征就是虚拟 DOM (virtual DOM)。呈现为HTML容器只是它在浏览器中的一个简单实现。最近引入的 React Native 证明了这一点。如果我们将我们的 canvas 布局引擎与 react 组件结合起来会咋样?React Canvas简介 使React组件拥有了渲染到canvas的能力第一个版本的 canvas 布局引擎看上去很像命令式的代码。如果你做过 JavaScript DOM 操作你可能会运行过这样的代码:// Create the parent layer
var root = RenderLayer.getPooled();
root.frame = [0, 0, 320, 480];
// Add an image
var image = RenderLayer.getPooled('image');
image.frame = [0, 0, 320, 200];
image.imageUrl = 'http://lorempixel.com/360/420/cats/1/';
root.addChild(image);
// Add some text
var label = RenderLayer.getPooled('text');
label.frame = [10, 210, 300, 260];
label.text = 'Lorem ipsum...';
label.fontSize = 18;
label.lineHeight = 24;
root.addChild(label);
当然,这样能完成效果,但是谁想这样写代码?除了容易出错,也很难想象出渲染结果使用React Canvas则变成下面这样:var MyComponent = React.createClass({
render: function () {
&Group style={styles.group}&
&Image style={styles.image} src='http://...' /&
&Text style={styles.text}&
Lorem ipsum...
var styles = {
width: 320,
height: 480
width: 320,
height: 200
width: 300,
height: 260,
fontSize: 18,
lineHeight: 24
您可能会注意到,一切都似乎是绝对定位实现的。确实是这样。我们的canvas渲染引擎的诞生,就伴随着驱动像素级布局,实现多行文本过长省略的使命。传统的CSS不能做到这一点,所以绝对定位的方式对我们来说很适合。然而,这种方法并不适合于所有应用程序。 css布局Facebook 最近开源的。他支持 CSS 的一些子集,包括 margin、padding,position 和最重要的 flexbox。将 css 布局整合到 React Canvas 只是一个时间问题。看看,看看我们是如何改变组件样式的。声明式的无限滚动你如何在 React Canvas 中创建一个达到60 fps ,无限,分页的滚动列表? 事实证明这实现起来非常容易,因为 react 会做虚拟 DOM 的 diff 。在render() 函数中只有当前可见的元素被返回,React负责更新滚动期间所需的虚拟 DOM 树。var ListView = React.createClass({
getInitialState: function () {
scrollTop: 0
render: function () {
var items = this.getVisibleItemIndexes().map(this.renderItem);
onTouchStart={this.handleTouchStart}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}
onTouchCancel={this.handleTouchEnd}&
renderItem: function (itemIndex) {
// Wrap each item in a &Group& which is translated up/down based on
// the current scroll offset.
var translateY = (itemIndex * itemHeight) - this.state.scrollT
var style = { translateY: translateY };
&Group style={style} key={itemIndex}&
getVisibleItemIndexes: function () {
// Compute the visible item indexes based on `this.state.scrollTop`.
为了勾住(捕获)滚动,我们在列表视图组件中,调用(滚动组件)的 setState() 方法。...
// Create the Scroller instance on mount.
componentDidMount: function () {
this.scroller = new Scroller(this.handleScroll);
// This is called by the Scroller at each scroll event.
handleScroll: function (left, top) {
this.setState({ scrollTop: top });
handleTouchStart: function (e) {
this.scroller.doTouchStart(e.touches, e.timeStamp);
handleTouchMove: function (e) {
e.preventDefault();
this.scroller.doTouchMove(e.touches, e.timeStamp, e.scale);
handleTouchEnd: function (e) {
this.scroller.doTouchEnd(e.timeStamp);
尽管这是一个简化版本,但展示了 React 一些最优秀的特性。触摸事件被声明式绑定在 render()函数中。每个 touchmove 事件被转发到 Scroller 中来计算当前滚动的偏移。每个 Scroller 发出的滚动事件则用于更新状态列表视图组件,只对当前屏幕可见的元素进行渲染。所有这一切发生在16ms以下,因为 react 的。你可以查看这个完整实现的源代码实际应用React Canvas 并不能完全取代 DOM。我们在我们的移动 web app 中,性能要求最关键的地方去使用,主要是滚动时间轴视图这部分。当渲染性能不是问题的时候, Dom 可能是一个更好的方法。事实上,对某些元素输入字段和音频/视频等,这是唯一的方法从某种意义上说, Flipboard 的移动 web 是一个混合( hybird )的应用程序。相比传统的原生应用和网络技术结合的方式, Flipboard 的内容全部是 web 内容。它的 UI 基于 dom 实现,并在适当的地方使用 canvas 渲染。可访问性这个领域需要进一步探索。使用降级内容( canvas 的 DOM 子树)应该允许 VoiceOver 这样的屏幕阅读器与内容交互。我们在测试的设备上看到了不同的结果。另外,关于也有标准,但目前暂时不被浏览器支持。在2009年提出的一种方法,是元素渲染到 canvas 时,同时维护一个,用于元素同步。我们正在继续研究实现可访问性的正确方法。结论在追求60 fps 的过程中,我们有时会采取极端措施。 Flipboard 为研究移动网络的局限性提供了一个案例。虽然这种方法可能并不适用于所有应用程序,我们将应用的交互和性能水平提升到可以与本地应用相竞争。我们希望通过开放我们在
中所做的工作,可以让其他引人注目的例子出现。用手机访问,体验一下。或者如果你没有 Flipboard 账户,体验一下 Flipboard 上的。请让我们获得你的想法。特别感谢 Charles, Eugene 和 Anh 的编辑和建议。原文:外刊君推荐阅读:105收藏分享举报文章被以下专栏收录关注前端技术,探寻深邃思想,https://qianduan.group{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\u002Fpay.zhihu.com\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&isPending&:false,&contributes&:[{&sourceColumn&:{&lastUpdated&:,&description&:&关注前端前沿技术,探寻业界深邃思想。https:\u002F\u002Fqianduan.group 欢迎微信\u002F微博搜索『前端外刊评论』,关注我们。欢迎给本专栏投稿,原作译作不限,要求:质量高!如果愿意尝试从事前端技术相关的书籍的编写或翻译工作,请私信外刊君。&,&permission&:&COLUMN_PUBLIC&,&memberId&:3313,&contributePermission&:&COLUMN_PUBLIC&,&translatedCommentPermission&:&all&,&canManage&:true,&intro&:&关注前端技术,探寻深邃思想,https:\u002F\u002Fqianduan.group&,&urlToken&:&FrontendMagazine&,&id&:699,&imagePath&:&bd21f286e6aa5e210b60e2e257ce890b.jpeg&,&slug&:&FrontendMagazine&,&applyReason&:&&,&name&:&前端外刊评论&,&title&:&前端外刊评论&,&url&:&https:\u002F\u002Fzhuanlan.zhihu.com\u002FFrontendMagazine&,&commentPermission&:&COLUMN_ALL_CAN_COMMENT&,&canPost&:true,&created&:,&state&:&COLUMN_NORMAL&,&followers&:43228,&avatar&:{&id&:&bd21f286e6aa5e210b60e2e257ce890b&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&activateAuthorRequested&:false,&following&:false,&imageUrl&:&https:\u002F\u002Fpic1.zhimg.com\u002Fbd21f286e6aa5e210b60e2e257ce890b_l.jpg&,&articlesCount&:210},&state&:&accepted&,&targetPost&:{&titleImage&:&https:\u002F\u002Fpic2.zhimg.com\u002Fd511bd38a87c81a627772_r.jpg&,&lastUpdated&:,&imagePath&:&d511bd38a87c81a627772&,&permission&:&ARTICLE_PUBLIC&,&topics&:[],&summary&:&在智能手机和平板电脑的黎明时期, Flipboard 推出“移动先行”的体验,使我们可以重新思考页面中内容布局的原则,以及与触摸屏相关的,如何获得更好的用户体验的因素。为了建立完整的体验,我们将 Flipboard 带到 web 端。我们在 Flipboard 所做的,在每台…&,&copyPermission&:&ARTICLE_COPYABLE&,&translatedCommentPermission&:&all&,&likes&:0,&origAuthorId&:70550,&publishedTime&:&T20:49:36+08:00&,&sourceUrl&:&&,&urlToken&:,&id&:168063,&withContent&:false,&slug&:,&bigTitleImage&:false,&title&:&指尖的流畅体验--基于canvas建立移动界面&,&url&:&\u002Fp\u002F&,&commentPermission&:&ARTICLE_ALL_CAN_COMMENT&,&snapshotUrl&:&&,&created&:,&comments&:0,&columnId&:699,&content&:&&,&parentId&:0,&state&:&ARTICLE_PUBLISHED&,&imageUrl&:&https:\u002F\u002Fpic2.zhimg.com\u002Fd511bd38a87c81a627772_r.jpg&,&author&:{&bio&:&西子湖畔&,&isFollowing&:false,&hash&:&5cb860ee3af779d19e81a&,&uid&:00,&isOrg&:false,&slug&:&jiang-tian-yi&,&isFollowed&:false,&description&:&github.com\u002Fjtyjty99999&,&name&:&姜天意&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fjiang-tian-yi&,&avatar&:{&id&:&da8e974dc&,&template&:&https:\u002F\u002Fpic4.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&memberId&:70550,&excerptTitle&:&&,&voteType&:&ARTICLE_VOTE_CLEAR&},&id&:263717}],&title&:&指尖的流畅体验--基于canvas建立移动界面&,&author&:&jiang-tian-yi&,&content&:&\u003Cblockquote\u003E\u003Cp\u003E在智能手机和平板电脑的黎明时期, Flipboard 推出“移动先行”的体验,使我们可以重新思考页面中内容布局的原则,以及与触摸屏相关的,如何获得更好的用户体验的因素。\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E为了建立完整的体验,我们将 Flipboard 带到 web 端。我们在 Flipboard 所做的,在每台用户使用的设备上都拥有独立的价值:整理那些来自你最关心的主题,来源以及人的最好的故事。因此把我们的服务带到web端,也是一个合乎逻辑的延伸。\u003C\u002Fp\u003E\u003Cp\u003E当我们开始这个项目后,认识到我们需要把源自移动体验的思考搬到 web 端,试图提升 web 端的内容布局和交互。我们想达到原生应用般的精致和性能,且仍能感知到真实的浏览器。\u003C\u002Fp\u003E\u003Cp\u003E早些时间,经过测试大量的产品原型后,我们决定使用滚动的方式作为 web 端体验。我们的移动应用被大家所熟知的是类似翻书般的体验,在触摸屏上这很直观。但一系列的原因表明,滚动在 web 端的体验更加自然。\u003C\u002Fp\u003E\u003Cp\u003E为了\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fwww.html5rocks.com\u002Fen\u002Ftutorials\u002Fspeed\u002Fscrolling\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E优化滚动的性能\u003C\u002Fa\u003E,我们知道我们需要保证页面渲染的频率低于16ms,同时限制回流(reflow)和重绘(repaints)。这在动画中尤其重要。为了避免动画中重新渲染,有两个属性你可以安全地作用于动画上: CSS transform 和 opacity。但这样选择余地太小了。\u003C\u002Fp\u003E\u003Cp\u003E当你想实现元素上宽度动画效果怎么办?\u003C\u002Fp\u003E\u003Cp\u003E(知乎的gif图片貌似有问题,大家可以访问 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fengineering.flipboard.com\u002Fassets\u002Fmobileweb\u002Ffollow_btn.gif\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eengineering.flipboard.com\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E\u002Fassets\u002Fmobileweb\u002Ffollow_btn.gif\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003C\u002Fa\u003E 看这张图)\u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\u002Fpic3.zhimg.com\u002F8b1635aef23ad5da3f5792_b.jpg\& data-rawwidth=\&371\& data-rawheight=\&122\& class=\&content_image\& width=\&371\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='371'%20height='122'&&\u002Fsvg&\& data-rawwidth=\&371\& data-rawheight=\&122\& class=\&content_image lazy\& width=\&371\& data-actualsrc=\&https:\u002F\u002Fpic3.zhimg.com\u002F8b1635aef23ad5da3f5792_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cp\u003E一帧帧的滚动动画如何处理?\u003C\u002Fp\u003E\u003Cp\u003E(知乎的gif图片貌似有问题,大家可以访问\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fengineering.flipboard.com\u002Fassets\u002Fmobileweb\u002Ftopbar.gif\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eengineering.flipboard.com\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E\u002Fassets\u002Fmobileweb\u002Ftopbar.gif\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003C\u002Fa\u003E 看这张图)\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\u002Fpic1.zhimg.com\u002Fe77e7efe2d1c4b2cec6b9f788b1ae2dc_b.jpg\& data-rawwidth=\&374\& data-rawheight=\&100\& class=\&content_image\& width=\&374\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='374'%20height='100'&&\u002Fsvg&\& data-rawwidth=\&374\& data-rawheight=\&100\& class=\&content_image lazy\& width=\&374\& data-actualsrc=\&https:\u002F\u002Fpic1.zhimg.com\u002Fe77e7efe2d1c4b2cec6b9f788b1ae2dc_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cbr\u003E\u003Cp\u003E(注意,在上图中,顶部的图标从白色到黑色。这里使用了两个单独的元素相互覆盖,根据下面的内容来互相裁剪)。 \u003Cbr\u003E\u003Cbr\u003E这些类型的动画一直在网上遭受\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fjankfree.org\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E诟病\u003C\u002Fa\u003E,特别是在移动设备上,只因为一个简单的原因: \u003Cbr\u003EDOM 太慢了。\u003Cbr\u003E这不是慢,是非常之慢。如果你在动画过程中,使用任何方式接触 DOM 元素,都会破坏掉16ms每帧这一过程。\u003C\u002Fp\u003E\u003Ch2\u003E开始使用 & canvas &\u003C\u002Fh2\u003E\u003Cp\u003E大多数现代移动设备都拥有硬件加速的 canvas,我们为什么不利用起来呢?\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fchrome.angrybirds.com\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EHTML5 游戏\u003C\u002Fa\u003E已经做到了。我们能真正在 canvas 上开发应用界面么?\u003C\u002Fp\u003E\u003Ch3\u003E立即模式与保留模式\u003C\u002Fh3\u003E\u003Cp\u003ECanvas 是一种立即模式的绘图 API,这意味着绘制时不保留所绘制对象的信息。与其相反的是保留模式,这是一种声明性的 API,维护所绘制对象的层次结构。\u003C\u002Fp\u003E\u003Cp\u003E保留模式 API 的优点是,对于你的应用程序,他们通常更容易构建复杂的场景,例如 DOM。通常这都会带来性能成本,需要额外的内存来保存场景和更新场景,这可能会很慢。\u003C\u002Fp\u003E\u003Cp\u003ECanvas 受益于立即模式,允许直接发送绘图命令到 GPU。但若用它来构建用户界面,需要进行一个更高层次的抽象。例如一些简单的处理,比如当绘制一个异步加载的资源到一个元素上时会出现问题,如在图片上绘制文本。在HTML中,由于元素存在顺序,以及 CSS 中存在 z-index,因此是很容易实现的。\u003C\u002Fp\u003E\u003Ch2\u003E在& canvas &元素中建立UI\u003C\u002Fh2\u003E\u003Cp\u003E相比 HTML+CSS,canvas 则有些先天不足,缺少非常多在 HTML + CSS 中理所当然的特性。\u003C\u002Fp\u003E\u003Ch3\u003E文本\u003C\u002Fh3\u003E\u003Cp\u003Ecanvas有一个很简单的 API 用于绘制文字:fillText(text, x, y [, maxWidth])\u003Cbr\u003E这个函数接受三个参数:文字本身以及绘制起点的x,y坐标。但 canvas 只能一次绘制一行文字。如果你需要让文字换行,需要自己写函数。\u003C\u002Fp\u003E\u003Ch3\u003E图片\u003C\u002Fh3\u003E\u003Cp\u003E你可以使用drawImage()函数在 canvas 上绘制图片。这是个可变参数函数,你可以指定更多参数,从而控制定位和裁切。但是canvas不在乎图像是否加载,或不能确定只在图像加载事件后调用函数。\u003C\u002Fp\u003E\u003Ch3\u003E元素层叠\u003C\u002Fh3\u003E\u003Cp\u003E通过 DOM 元素的顺序或使用 CSS 的 z-index属性,在 HTML 和 CSS 指定一个元素是否在另一个上应该很容易。但请记住,canvas 是立即模式的绘图 API。当元素重叠或者其中一个需要重绘时,都必须以同一顺序重新绘制(或至少局部重绘)。\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E译者注:关于局部重绘提高性能的文章大家可以参考\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fwww.cnblogs.com\u002Frhcad\u002Farchive\u002F\u002F17\u002F2774794.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E提高HTML5 canvas性能的几种方法(转)\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Ch3\u003E自定义字体\u003C\u002Fh3\u003E\u003Cp\u003E需要使用一个自定义 web 字体吗? canvas 的文本 API 并不在乎字体是否加载。你需要一种方法来知道一个字体是否加载,并绘制任何依赖此字体的区域。幸运的是,现代浏览器有一个\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fdev.w3.org\u002Fcsswg\u002Fcss-font-loading\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E基于promise的API\u003C\u002Fa\u003E。不幸的是, iOS WebKit (iOS 8 时)不支持它。\u003C\u002Fp\u003E\u003Ch3\u003E& canvas &的优点\u003C\u002Fh3\u003E\u003Cp\u003E鉴于所有这些缺点,人们开始质疑 canvas 来代替 DOM 这一选择。最终,我们的讨论由一个很简单的真理来决定:\u003C\u002Fp\u003E\u003Cp\u003E你不能基于 dom 建立一个60 fps的滚动列表视图。\u003C\u002Fp\u003E\u003Cp\u003E许多人(包括我们)已经尝试过,但都失败了。可滚动的元素可以在纯 HTML 和 CSS 中通过overflow:scroll 实现:(结合 IOS 上的 -webkit-overflow-scrolling:touch ),但这些不能在滚动动画中给予你逐帧控制,同时移动浏览器很难处理又长又复杂的内容。\u003C\u002Fp\u003E\u003Cp\u003E为了构建一个内含相当复杂的内容的无限滚动列表,我们需要在 web 端实现一个\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fdeveloper.apple.com\u002Flibrary\u002Fios\u002Fdocumentation\u002FUIKit\u002FReference\u002FUITableView_Class\u002Findex.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EUITableView\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cp\u003E译者注 UITableView 是 IOS 控件\u003C\u002Fp\u003E\u003C\u002Fblockquote\u003E\u003Cp\u003E与 DOM 相比,今天的大多数设备都有基于硬件加速的 canvas 实现,可以直接发送绘图命令到 GPU。这意味着我们可以非常快的渲染元素;在许多情况下,我们所说的是毫秒级的范围。\u003C\u002Fp\u003E\u003Cp\u003E相比 HTML + CSS , canvas 也是一个非常“苗条”的 API ,这减少了界面上的 bug 或浏览器之间的不一致性。有一个理由更加直接,canvas 没有 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fcaniuse.com\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003ECan I Use?\u003C\u002Fa\u003E。\u003C\u002Fp\u003E\u003Ch2\u003E更快的 DOM 抽象\u003C\u002Fh2\u003E\u003Cp\u003E如前所述,为了有点效果,我们需要一个更高层次的抽象,而不是简单地绘制矩形、文本和图像。我们构建了一个非常小的抽象,允许开发人员处理一个节点树,而不是处理一个严格的绘图命令序列。\u003C\u002Fp\u003E\u003Ch3\u003E渲染层\u003C\u002Fh3\u003E\u003Cp\u003E渲染层( RenderLayer )是基本节点,其他节点建立在其上。常见的属性如 top,left,width,height,backgroundColor 和 zIndex 在这个层展现。 RenderLayer 只不过是一个普通的 JavaScript 对象,包含这些属性和一个子元素数组。 \u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch3\u003E图像\u003C\u002Fh3\u003E\u003Cp\u003E我们使用图像层的附加属性来指定图像 URL 和信息。你不必担心图像加载事件的监听,图像层会处理后将一个信号发送到绘图引擎来表示图片需要更新。   \u003C\u002Fp\u003E\u003Ch3\u003E文本\u003C\u002Fh3\u003E\u003Cp\u003E文本层可以显示多行文本截断,这在 DOM 里处理成本非常高。文本层还支持自定义字体,也会处理当字体加载完毕后更新的动作。\u003C\u002Fp\u003E\u003Ch3\u003E合成\u003C\u002Fh3\u003E\u003Cp\u003E这些层可以合成起来以便构建复杂的界面。下面是一个渲染层树\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E{\n
frame: [0, 0, 320, 480],\n
backgroundColor: '#fff',\n
children: [\n
type: 'image',\n
frame: [0, 0, 320, 200],\n
imageUrl: 'http:\u002F\u002Florempixel.com\u002F360\u002F420\u002Fcats\u002F1\u002F'\n
type: 'text',\n
frame: [10, 210, 300, 260],\n
text: 'Lorem ipsum...',\n
fontSize: 18,\n
lineHeight: 24\n
]\n}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Ch3\u003E无效层\u003C\u002Fh3\u003E\u003Cp\u003E当一个层需要重绘,例如一个图像加载后,它发送一个信号到绘图引擎表示其框架是脏( dirty )的。这些修改使用 requestAnimationFrame来批量处理,避免\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fwilsonpage.co.uk\u002Fpreventing-layout-thrashing\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E布局抖动\u003C\u002Fa\u003E,之后在下一帧画布重绘。\u003C\u002Fp\u003E\u003Ch2\u003E达到60fps的滚动\u003C\u002Fh2\u003E\u003Cp\u003E对于 web 端,也许我们最理所当然关注的,是浏览器如何来滚动网页。浏览器厂商已经\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fwww.chromium.org\u002Fdevelopers\u002Fdesign-documents\u002Fgpu-accelerated-compositing-in-chrome\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E竭尽全力\u003C\u002Fa\u003E提高滚动性能。\u003C\u002Fp\u003E\u003Cp\u003E这其实是一个妥协。为了达到60 fps 的滚动指标,移动浏览器在执行滚动期间,停止 JavaScript 的执行,这是怕 DOM 修改导致回流。最近, IOS 和 Android 暴露了 onScroll 事件,他们的工作过程更像桌面浏览器了,但如果你试图在滚动时保持 DOM 元素的位置同步,具体的实现可能会有差别。\u003C\u002Fp\u003E\u003Cp\u003E幸运的是,浏览器厂商已经意识到这个问题。特别是 Chrome 团队已经\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fupdates.html5rocks.com\u002F\u002FA-More-Compatible-Smoother-Touch\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E开放\u003C\u002Fa\u003E了为了改善手机端这种情况所做的工作。\u003C\u002Fp\u003E\u003Cp\u003E回到 canvas ,简短的回答是你必须用 JavaScript 实现滚动。\u003C\u002Fp\u003E\u003Cp\u003E你首先需要的是一种计算滚动程度的算法。如果你不想研究数学,Zynga 开源的\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Fzynga\u002Fscroller\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E纯滚动实现\u003C\u002Fa\u003E,适合任何类似此布局的情况。\u003C\u002Fp\u003E\u003Cp\u003E我们使用一个 canvas 元素来完成滚动。在每一个触摸事件时,根据当前的滚动程度去更新渲染树。之后,整个渲染树使用新的坐标来重新渲染。\u003C\u002Fp\u003E\u003Cp\u003E这听起来令人难以置信的慢,在 canvas 上可以使用一个重要的优化技术–画布上绘图操作的结果可以在离屏层(off-screen)canvas 被缓存。离屏层(off-screen)之后可以用来重新绘制层。\u003C\u002Fp\u003E\u003Cp\u003E这种技术不仅可以用于图像层,文本和图形也适用。两个成本最高的绘图操作是填充文本和图像。但是一旦这些层绘制一次以后,接下来使用离屏层重新绘制他们是非常快的。\u003C\u002Fp\u003E\u003Cp\u003E(注意,这里图片比较大,知乎无法上传,大家可以访问 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fengineering.flipboard.com\u002Fassets\u002Fmobileweb\u002Fscrolling.gif\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eengineering.flipboard.com\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E\u002Fassets\u002Fmobileweb\u002Fscrolling.gif\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003C\u002Fa\u003E)\u003C\u002Fp\u003E\u003Cp\u003E在上面的演示中,每个页面的内容分为两层:图像层和文本层。文本层包含多个元素组合在一起。每一帧滚动动画中,这两层都使用位图缓存来重绘。   \u003C\u002Fp\u003E\u003Ch3\u003E对象池\u003C\u002Fh3\u003E\u003Cp\u003E在一个无限列表的滚动过程中,大量的 RenderLayers 会被建立和销毁。这会在内存中创建大量的垃圾,当进行垃圾收集时将停止主线程。\u003C\u002Fp\u003E\u003Cp\u003E为了避免产生大量垃圾, RenderLayers 与相关对象汇集到一个池中。这意味着只有相对较少的层对象被创建。当不再需要时,它会被释放回池中,之后可以重用。   \u003C\u002Fp\u003E\u003Ch3\u003E极速快照\u003C\u002Fh3\u003E\u003Cp\u003E缓存复合层的特性可以带来另一个优势:能够将渲染的部分结构作为一个位图。你有没有建立部分 DOM 结构快照的需求?当你将这些结构渲染在 canvas 时,速度会快得令人难以置信。 \u003Cbr\u003E\u003Cbr\u003E这个将一个项目放入一本杂志的界面,利用了这种特性来执行一个时间轴维度的平稳过渡。快照包含去掉顶部和底部的整个项目。\u003C\u002Fp\u003E\u003Cp\u003E(知乎的gif图显示貌似有问题,大家可以访问这个链接看下面这张图\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fengineering.flipboard.com\u002Fassets\u002Fmobileweb\u002Fflip_ui.gif\& class=\& external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E\u003Cspan class=\&invisible\&\u003Ehttp:\u002F\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&visible\&\u003Eengineering.flipboard.com\u003C\u002Fspan\u003E\u003Cspan class=\&invisible\&\u003E\u002Fassets\u002Fmobileweb\u002Fflip_ui.gif\u003C\u002Fspan\u003E\u003Cspan class=\&ellipsis\&\u003E\u003C\u002Fspan\u003E\u003C\u002Fa\u003E)\u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\u002Fpic4.zhimg.com\u002Ff5374176affdf8565ad53f_b.jpg\& data-rawwidth=\&340\& data-rawheight=\&577\& class=\&content_image\& width=\&340\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='340'%20height='577'&&\u002Fsvg&\& data-rawwidth=\&340\& data-rawheight=\&577\& class=\&content_image lazy\& width=\&340\& data-actualsrc=\&https:\u002F\u002Fpic4.zhimg.com\u002Ff5374176affdf8565ad53f_b.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Ch2\u003E一个声明式的 API\u003C\u002Fh2\u003E\u003Cp\u003E现在我们已经拥有了构建一个应用程序的砖块。然而,通过命令来构建 RenderLayers 可能是乏味的。如果我们有个类似于DOM工作方式的声明式 API 不是很好么? \u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch3\u003EReact\u003C\u002Fh3\u003E\u003Cp\u003E我们是 React 框架的忠实粉丝。它的单一定向的数据流和声明式API已经改变了人们构建应用程序的方式。react最引人注目的特征就是虚拟 DOM (virtual DOM)。呈现为HTML容器只是它在浏览器中的一个简单实现。最近引入的 React Native 证明了这一点。\u003C\u002Fp\u003E\u003Cp\u003E如果我们将我们的 canvas 布局引擎与 react 组件结合起来会咋样?\u003C\u002Fp\u003E\u003Ch3\u003EReact Canvas简介\u003C\u002Fh3\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Fflipboard\u002Freact-canvas\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EReact Canvas\u003C\u002Fa\u003E 使React组件拥有了渲染到canvas的能力\u003C\u002Fp\u003E\u003Cp\u003E第一个版本的 canvas 布局引擎看上去很像命令式的代码。如果你做过 JavaScript DOM 操作你可能会运行过这样的代码:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u002F\u002F Create the parent layer\nvar root = RenderLayer.getPooled();\nroot.frame = [0, 0, 320, 480];\n\n\u002F\u002F Add an image\nvar image = RenderLayer.getPooled('image');\nimage.frame = [0, 0, 320, 200];\nimage.imageUrl = 'http:\u002F\u002Florempixel.com\u002F360\u002F420\u002Fcats\u002F1\u002F';\nroot.addChild(image);\n\n\u002F\u002F Add some text\nvar label = RenderLayer.getPooled('text');\nlabel.frame = [10, 210, 300, 260];\nlabel.text = 'Lorem ipsum...';\nlabel.fontSize = 18;\nlabel.lineHeight = 24;\nroot.addChild(label);\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E当然,这样能完成效果,但是谁想这样写代码?除了容易出错,也很难想象出渲染结果\u003C\u002Fp\u003E\u003Cp\u003E使用React Canvas则变成下面这样:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Evar MyComponent = React.createClass({\n
render: function () {\n
return (\n
&Group style={styles.group}&\n
&Image style={styles.image} src='http:\u002F\u002F...' \u002F&\n
&Text style={styles.text}&\n
Lorem ipsum...\n
&\u002FText&\n
&\u002FGroup&\n
}\n});\n\nvar styles = {\n
group: {\n
left: 0,\n
width: 320,\n
height: 480\n
image: {\n
left: 0,\n
width: 320,\n
height: 200\n
left: 10,\n
top: 210,\n
width: 300,\n
height: 260,\n
fontSize: 18,\n
lineHeight: 24\n
}\n};\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E您可能会注意到,一切都似乎是绝对定位实现的。确实是这样。我们的canvas渲染引擎的诞生,就伴随着驱动像素级布局,实现多行文本过长省略的使命。传统的CSS不能做到这一点,所以绝对定位的方式对我们来说很适合。然而,这种方法并不适合于所有应用程序。 \u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003Ecss布局\u003C\u002Fh2\u003E\u003Cp\u003EFacebook 最近开源的\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Ffacebook\u002Fcss-layout\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003ECSS的JavaScript实现\u003C\u002Fa\u003E。他支持 CSS 的一些子集,包括 margin、padding,position 和最重要的 flexbox。\u003C\u002Fp\u003E\u003Cp\u003E将 css 布局整合到 React Canvas 只是一个时间问题。看看\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Fflipboard\u002Freact-canvas\u002Fblob\u002Fmaster\u002Fexamples\u002Fcss-layout\u002Fapp.js\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E这个例子\u003C\u002Fa\u003E,看看我们是如何改变组件样式的。\u003C\u002Fp\u003E\u003Ch2\u003E声明式的无限滚动\u003C\u002Fh2\u003E\u003Cp\u003E你如何在 React Canvas 中创建一个达到60 fps ,无限,分页的滚动列表? \u003Cbr\u003E\u003Cbr\u003E事实证明这实现起来非常容易,因为 react 会做虚拟 DOM 的 diff 。在render() 函数中只有当前可见的元素被返回,React负责更新滚动期间所需的虚拟 DOM 树。\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Evar ListView = React.createClass({\n
getInitialState: function () {\n
return {\n
scrollTop: 0\n
render: function () {\n
var items = this.getVisibleItemIndexes().map(this.renderItem);\n
return (\n
onTouchStart={this.handleTouchStart}\n
onTouchMove={this.handleTouchMove}\n
onTouchEnd={this.handleTouchEnd}\n
onTouchCancel={this.handleTouchEnd}&\n
&\u002FGroup&\n
renderItem: function (itemIndex) {\n
\u002F\u002F Wrap each item in a &Group& which is translated up\u002Fdown based on\n
\u002F\u002F the current scroll offset.\n
var translateY = (itemIndex * itemHeight) - this.state.scrollT\n
var style = { translateY: translateY };\n
return (\n
&Group style={style} key={itemIndex}&\n
&Item \u002F&\n
&\u002FGroup&\n
getVisibleItemIndexes: function () {\n
\u002F\u002F Compute the visible item indexes based on `this.state.scrollTop`.\n
}\n});\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E为了勾住(捕获)滚动,我们在列表视图组件中,调用\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Fzynga\u002Fscroller\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EScroller\u003C\u002Fa\u003E(滚动组件)的 setState() 方法。\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E...\n\n\u002F\u002F Create the Scroller instance on mount.\ncomponentDidMount: function () {\n
this.scroller = new Scroller(this.handleScroll);\n},\n\n\u002F\u002F This is called by the Scroller at each scroll event.\nhandleScroll: function (left, top) {\n
this.setState({ scrollTop: top });\n},\n\nhandleTouchStart: function (e) {\n
this.scroller.doTouchStart(e.touches, e.timeStamp);\n},\n\nhandleTouchMove: function (e) {\n
e.preventDefault();\n
this.scroller.doTouchMove(e.touches, e.timeStamp, e.scale);\n},\n\nhandleTouchEnd: function (e) {\n
this.scroller.doTouchEnd(e.timeStamp);\n}\n\n...\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E尽管这是一个简化版本,但展示了 React 一些最优秀的特性。触摸事件被声明式绑定在 render()函数中。每个 touchmove 事件被转发到 Scroller 中来计算当前滚动的偏移。每个 Scroller 发出的滚动事件则用于更新状态列表视图组件,只对当前屏幕可见的元素进行渲染。所有这一切发生在16ms以下,因为 react 的\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fcalendar.perfplanet.com\u002FFdiff\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E diff 算法非常快\u003C\u002Fa\u003E。\u003C\u002Fp\u003E\u003Cp\u003E你可以查看这个\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Fflipboard\u002Freact-canvas\u002Fblob\u002Fmaster\u002Flib\u002FListView.js\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E滚动列表\u003C\u002Fa\u003E完整实现的源代码\u003C\u002Fp\u003E\u003Ch2\u003E实际应用\u003C\u002Fh2\u003E\u003Cp\u003EReact Canvas 并不能完全取代 DOM。我们在我们的移动 web app 中,性能要求最关键的地方去使用,主要是滚动时间轴视图这部分。\u003C\u002Fp\u003E\u003Cp\u003E当渲染性能不是问题的时候, Dom 可能是一个更好的方法。事实上,对某些元素输入字段和音频\u002F视频等,这是唯一的方法\u003C\u002Fp\u003E\u003Cp\u003E从某种意义上说, Flipboard 的移动 web 是一个混合( hybird )的应用程序。相比传统的原生应用和网络技术结合的方式, Flipboard 的内容全部是 web 内容。它的 UI 基于 dom 实现,并在适当的地方使用 canvas 渲染。\u003C\u002Fp\u003E\u003Ch2\u003E可访问性\u003C\u002Fh2\u003E\u003Cp\u003E这个领域需要进一步探索。使用降级内容( canvas 的 DOM 子树)应该允许 VoiceOver 这样的屏幕阅读器与内容交互。我们在测试的设备上看到了不同的结果。另外,关于\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fwww.w3.org\u002FTR\u002FFWD-2dcontext-2F%23dom-context-2d-drawfocusring\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E焦点的管理\u003C\u002Fa\u003E也有标准,但目前暂时不被浏览器支持。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fvimeo.com\u002F3195079\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EBespin\u003C\u002Fa\u003E在2009年提出的一种方法,是元素渲染到 canvas 时,同时维护一个\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Frobertnyman.com\u002F\u002F03\u002Fmozilla-labs-online-code-editor-bespin\u002F%23comment-560310\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E平行Dom\u003C\u002Fa\u003E,用于元素同步。我们正在继续研究实现可访问性的正确方法。\u003C\u002Fp\u003E\u003Ch2\u003E结论\u003C\u002Fh2\u003E\u003Cp\u003E在追求60 fps 的过程中,我们有时会采取极端措施。 Flipboard 为研究移动网络的局限性提供了一个案例。虽然这种方法可能并不适用于所有应用程序,我们将应用的交互和性能水平提升到可以与本地应用相竞争。我们希望通过开放我们在 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fgithub.com\u002Fflipboard\u002Freact-canvas\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EReact Canvas\u003C\u002Fa\u003E 中所做的工作,可以让其他引人注目的例子出现。\u003C\u002Fp\u003E\u003Cp\u003E用手机访问\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fflipboard.com\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003Eflipboard.com\u003C\u002Fa\u003E,体验一下。或者如果你没有 Flipboard 账户,体验一下 Flipboard 上\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fflipboard.com\u002F%40flipboard\u002Fflipboard-picks-8a1uu7ngz\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E一系列\u003C\u002Fa\u003E的\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fflipboard.com\u002F%40flipboard\u002Ften-for-today-k6ln1khuz\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E杂志\u003C\u002Fa\u003E。请让我们获得你的想法。\u003C\u002Fp\u003E\u003Cp\u003E特别感谢 Charles, Eugene 和 Anh 的编辑和建议。\u003C\u002Fp\u003E\u003Cp\u003E原文:\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fengineering.flipboard.com\u002F\u002Fmobile-web\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E60fps on the mobile web\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E外刊君推荐阅读:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fwww.w3cplus.com\u002Fperformance\u002Fbeginners-guide-to-perceived-performance.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E让手机站点像原生应用的四大途径\u003C\u002Fa\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\u002Fpic1.zhimg.com\u002F0c4fe5e5bea2bf530506b_b.jpg\& data-rawwidth=\&600\& data-rawheight=\&320\& class=\&origin_image zh-lightbox-thumb\& width=\&600\& data-original=\&https:\u002F\u002Fpic1.zhimg.com\u002F0c4fe5e5bea2bf530506b_r.jpg\&\u003E\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='600'%20height='320'&&\u002Fsvg&\& data-rawwidth=\&600\& data-rawheight=\&320\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&600\& data-original=\&https:\u002F\u002Fpic1.zhimg.com\u002F0c4fe5e5bea2bf530506b_r.jpg\& data-actualsrc=\&https:\u002F\u002Fpic1.zhimg.com\u002F0c4fe5e5bea2bf530506b_b.jpg\&\u003E\u003C\u002Ffigure\u003E&,&updated&:new Date(&T12:49:36.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:9,&collapsedCount&:0,&likeCount&:105,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&https:\u002F\u002Fpic2.zhimg.com\u002Fd511bd38a87c81a627772_r.jpg&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&reviewers&:[],&topics&:[],&adminClosedComment&:false,&titleImageSize&:{&width&:1214,&height&:927},&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&column&:{&slug&:&FrontendMagazine&,&name&:&前端外刊评论&},&tipjarState&:&inactivated&,&annotationAction&:[],&sourceUrl&:&&,&pageCommentsCount&:9,&hasPublishingDraft&:false,&snapshotUrl&:&&,&publishedTime&:&T20:49:36+08:00&,&url&:&\u002Fp\u002F&,&lastestLikers&:[{&bio&:&hope&,&isFollowing&:false,&hash&:&e2ea7eac1c&,&uid&:48,&isOrg&:false,&slug&:&liang-zhu-58-32&,&isFollowed&:false,&description&:&&,&name&:&Peter Chu&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fliang-zhu-58-32&,&avatar&:{&id&:&v2-e604adc8d940f17dfd4124132dce670f&,&template&:&https:\u002F\u002Fpic3.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&前端 强迫症患者&,&isFollowing&:false,&hash&:&501eebc4e2c576cd165b7c&,&uid&:016100,&isOrg&:false,&slug&:&lilijialiang&,&isFollowed&:false,&description&:&https:\u002F\u002Flilijialiang.com&,&name&:&李家良&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Flilijialiang&,&avatar&:{&id&:&faf571ecd94b8e4f601e3&,&template&:&https:\u002F\u002Fpic2.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&&,&isFollowing&:false,&hash&:&dc1a857c6be4e1aa0efbfe32&,&uid&:92,&isOrg&:false,&slug&:&&,&isFollowed&:false,&description&:&&,&name&:&惘闻的螺丝刀&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002F&,&avatar&:{&id&:&v2-5f4f62c21e7c957beb5e2&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&大学公益社团老兵、开源爱好者、Web 前端工程师&,&isFollowing&:false,&hash&:&494f1f3620beb7a14767&,&uid&:32,&isOrg&:false,&slug&:&Tech_Query&,&isFollowed&:false,&description&:&小吃货、特困生、渣情商……&,&name&:&水歌&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002FTech_Query&,&avatar&:{&id&:&1cdf30d88&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&前端开发&,&isFollowing&:false,&hash&:&6559acc12a9de4ef6ddc8&,&uid&:722240,&isOrg&:false,&slug&:&zhang-jin-xin-38&,&isFollowed&:false,&description&:&&,&name&:&张金新&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fzhang-jin-xin-38&,&avatar&:{&id&:&b4c316ced8a667cd87b41bfa&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}],&summary&:&\u003Cimg src=\&https:\u002F\u002Fpic3.zhimg.com\u002F8b1635aef23ad5da3f.jpg\& data-rawwidth=\&371\& data-rawheight=\&122\& class=\&origin_image inline-img zh-lightbox-thumb\& data-original=\&https:\u002F\u002Fpic3.zhimg.com\u002F8b1635aef23ad5da3f5792_r.jpg\&\u003E在智能手机和平板电脑的黎明时期, Flipboard 推出“移动先行”的体验,使我们可以重新思考页面中内容布局的原则,以及与触摸屏相关的,如何获得更好的用户体验的因素。为了建立完整的体验,我们将 Flipboard 带到 web 端。我们在 Flipboard 所做的,在每台…&,&reviewingCommentsCount&:0,&meta&:{&previous&:{&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&https:\u002F\u002Fpic1.zhimg.com\u002F50\u002F188cf571a898b5c09b044_xl.jpg&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&topics&:[{&url&:&https:\u002F\u002Fwww.zhihu.com\u002Ftopic\u002F&,&id&:&&,&name&:&CSS3&},{&url&:&https:\u002F\u002Fwww.zhihu.com\u002Ftopic\u002F&,&id&:&&,&name&:&用户体验设计&},{&url&:&https:\u002F\u002Fwww.zhihu.com\u002Ftopic\u002F&,&id&:&&,&name&:&前端开发&}],&adminClosedComment&:false,&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&author&:{&bio&:&&,&isFollowing&:false,&hash&:&13b5ee0e0b7dc917e688de9&,&uid&:36,&isOrg&:false,&slug&:&fanhc&,&isFollowed&:false,&description&:&&,&name&:&范洪春&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Ffanhc&,&avatar&:{&id&:&v2-ea9c3661f74abd07c0b5ff&,&template&:&https:\u002F\u002Fpic2.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&column&:{&slug&:&FrontendMagazine&,&name&:&前端外刊评论&},&content&:&\u003Cp\u003E\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fdemosthenes.info\u002Fblog\u002Fcss\u002Fflexbox\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EFlexbox\u003C\u002Fa\u003E 是一个强大的新生 CSS 布局模块,完全脱离于传统的 web 开发实践。网上有很多相关的文章,大都关注于规范的细节,导致文章冗长,难懂,甚至有些晦涩。相反,对于\u003Cstrong\u003E设计师和开发者如何使用 flexbox 解决布局问题\u003C\u002Fstrong\u003E的讨论相当少:至此,这篇文章出现了。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E从基础层面上来讲,\u003Cstrong\u003Eflexbox 有三个特性是设计之根本\u003C\u002Fstrong\u003E。但也是在很长一段时间内单纯使用 CSS 很难或者不可能完成任务:对齐方式,排布和顺序。\u003C\u002Fp\u003E\u003Cp\u003E开始之前,需要先了解下面几个事情:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003EFlexbox 在最终形成今天的规范之前,历经了三次迭代。每一次迭代都伴随着不同的属性名,在不同浏览器下有着相应的\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fdemosthenes.info\u002Fblog\u002F217\u002FCSS-Vendor-Prefixes-and-Flags\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E特定前缀\u003C\u002Fa\u003E。而现在,我们所处在这样的时刻,所有的浏览器都支持无前缀的终极规范,但是想要兼容低版本的浏览器还有很多坑要填。正因如此,我强烈建议你\u003Cstrong\u003E按照 flexbox 的最终规范编写代码,并且使用最新的浏览器进行测试,\u003C\u002Fstrong\u003E然后再去实现向前兼容。想要让你编写的代码同时兼容所有的浏览器是一件很头疼的事。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E尽管 flexbox 可以和其它的 CSS 布局系统一同工作,但是在开始使用新的系统之前,\u003Cstrong\u003E丢掉以前在 web 布局中的假设和实践\u003C\u002Fstrong\u003E很重要。这是一种全新的工作方式,如果坚持以前的思维,你将受到阻碍。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003E你可能偶尔听到“flexbox 是用来干啥干啥的”。诚然,其它的布局系统会很快的补充上 flexbox——比如 grids 和 regions,但这种称述并不完全准确。CSS 不是语义化的,没有哪一个 CSS 特性就是固定做某件事情的。你可以使用任意的 CSS 来完成你的需求;唯一的问题是什么样的代码才能更高效的实现目标。正如我们看到的,flexbox 解决了设计者在布局上正面临的诸多问题。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cp\u003EFlexbox 以前的几个版本给现在的开发者们带来了一些风险:很可能读到一篇没有指明书写时间的文章,最后发现自己正在看 2009 年的 flexbox 规范说明(现在已经废除)。所以,时刻谨慎小心,提高警惕。\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ch2\u003E一个简单的 Flexbox 应用实例\u003C\u002Fh2\u003E\u003Cp\u003E将三个简单的 &div& 元素放在另一个容器内部:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-html\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E \u003Cspan class=\&na\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E\&item-container\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E \u003Cspan class=\&na\&\u003Eclass\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E\&circle\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&&\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E \u003Cspan class=\&na\&\u003Eclass\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E\&square\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&&\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E \u003Cspan class=\&na\&\u003Eclass\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E\&circle\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&&\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E&\u002F\u003C\u002Fspan\u003E\u003Cspan class=\&nt\&\u003Ediv\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E现在按照内层的 div 元素的类名添加 CSS 样式,为外层容器添加 display: flex:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-css\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&nn\&\u003E#item-container\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Edisplay\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eflex\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Ebackground-color\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ehsl\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E34\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E88%\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E90%\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003Cspan class=\&nc\&\u003E.square\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Ewidth\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&m\&\u003E200px\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Eheight\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&m\&\u003E200px\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Ebackground-color\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ehsl\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E50\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E88%\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E50%\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003Cspan class=\&nc\&\u003E.circle\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Eborder\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eradius\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&m\&\u003E50%\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Ewidth\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&m\&\u003E150px\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Eheight\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&m\&\u003E150px\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Ebackground-color\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ehsl\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E22\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E88%\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E,\u003C\u002Fspan\u003E\u003Cspan class=\&m\&\u003E50%\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E结果如下所示:\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cfigure\u003E\u003Cimg src=\&https:\u002F\u002Fpic1.zhimg.com\u002F435e646f28c94dabbe90e7_b.jpg\& data-rawwidth=\&1444\& data-rawheight=\&384\& class=\&origin_image zh-lightbox-thumb\& width=\&1444\& data-original=\&https:\u002F\u002Fpic1.zhimg.com\u002F435e646f28c94dabbe90e7_r.jpg\&\u003E\u003C\u002Ffigure\u003E\u003Cp\u003E此时,有几件事需要注意:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cp\u003E很多常见的 flex 相关的属性都被应用于父元素,而不是单个的子元素。这通常会引起一个疑惑,绝大多数开发者习惯于控制单个的元素,而不是通过父元素为子元素添加样式;\u003C\u002Fp\u003E\u003C\u002Fli\u003E\u003Cli\u003EFlex 容器的每个直接子元素被称为一个“flex-item”。然而,子元素里面的所有元素不会继承任何特殊的样式,并且可以添加任何你想要的 CSS。因为每个子元素是一个 flex-item,你会发现自己通过将元素包裹在一个 div 或者其它的元素中,“保护”里面的内容。使该元素成为 flex-child,而不是它里面的内容;\u003C\u002Fli\u003E\u003Cli\u003E“Block”,“inline”,“float” 和 “text-align” 在 flex-item 的环境下无意义;\u003C\u002Fli\u003E\u003Cli\u003E默认情况,所有的 flex-item 会按照 flex 容器的\u003Cstrong\u003E顶部\u003C\u002Fstrong\u003E和\u003Cstrong\u003E左侧\u003C\u002Fstrong\u003E对齐。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ch2\u003EFlex 对齐方式\u003Cbr\u003E\u003C\u002Fh2\u003E\u003Cp\u003E第一步,我们要改变 flex-item 的水平对齐方式。flex-item 默认是左对齐,对应的 CSS 样式为 flex-start。我们将它改为末端。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-css\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&nn\&\u003E#item-container\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \n
\u003Cspan class=\&nb\&\u003Ejustify\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Econtent\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eflex\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eend\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cfigure\u003E\u003Cimg src=\&https:\u002F\u002Fpic1.zhimg.com\u002Faa7f6c8c7c3012c39fdf59_b.jpg\& data-rawwidth=\&1448\& data-rawheight=\&384\& cla

我要回帖

更多关于 canvas 简单动画 的文章

 

随机推荐