通过jsonp跨域

现在问题来了?什么是jsonp?维基百科的定义是:JSONP(JSON with Padding)是资料格式
JSON 的一种“使用模式”,可以让网页从别的网域要资料。

JSONP也叫填充式JSON,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如:

callback({“name”,”trigkit4″});

1
callback({"name","trigkit4"});

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。

在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
例如:

<script type=”text/javascript”> function dosomething(jsondata){
//处理获得的json数据 } </script> <script
src=”;

1
2
3
4
5
6
<script type="text/javascript">
    function dosomething(jsondata){
        //处理获得的json数据
    }
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>

js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。

PHP

<?php $callback = $_GET[‘callback’];//得到回调函数名 $data =
array(‘a’,’b’,’c’);//要返回的数据 echo
$callback.'(‘.json_encode($data).’)’;//输出 ?>

1
2
3
4
5
<?php
$callback = $_GET[‘callback’];//得到回调函数名
$data = array(‘a’,’b’,’c’);//要返回的数据
echo $callback.'(‘.json_encode($data).’)’;//输出
?>

最终,输出结果为:dosomething(['a','b','c']);

如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。

<script type=”text/javascript”> function dosomething(jsondata){
//处理获得的json数据 } </script> <script
src=”;

1
2
3
4
5
6
<script type="text/javascript">
    function dosomething(jsondata){
        //处理获得的json数据
    }
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>

jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。

Why Not Redux

Redux 很好,而且社区也有很多跟除 React
之外的视图层集成的实践。单纯的比较 Redux 跟 MobX
大概需要再写一篇文章来阐述,这里只简单说几点与视图层集成时的差异:

  1. 虽然 Redux 本质也是一个观察者模型,但是在 Redux
    的实现下,状态的变化并不是通过数据 diff
    得出而是 dispatch(action) 来手动通知的,而真正的 diff
    则交给了视图层,这不仅导致可能的渲染浪费(并不是所有 library 都有
    vdom),在处理各种需要在变化时触发副作用的场景也会显得过于繁琐。
  2. 由于第一条 Redux 不做数据
    diff,因此我们无法在视图层接手数据前得知哪个局部被更新,进而无法更高效的选择性更新视图。
  3. Redux 在 store 的设计上是 opinionated
    的,它奉行 单一 store 原则。应用可以完全由状态数据来描述、且状态可管理可回溯
    这一点上我没有意见,但并不是只有单一 store这一条出路,多 store
    依然能达成这一目标。显然 mobx 在这一点上是 unopinionated
    且灵活性更强。
  4. Redux
    概念太多而自身做的又太少。可以对比一下 ngRedux 跟 mobx-angularjs 看看实现复杂度上的差异。

关于作者:chokcoco

图片 1

经不住流年似水,逃不过此间少年。

个人主页 ·
我的文章 ·
63 ·
   

图片 2

跨域资源共享(CORS)

CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

<script type=”text/javascript”> var xhr = new XMLHttpRequest();
xhr.open(“GET”, “/trigkit4”,true); xhr.send(); </script>

1
2
3
4
5
<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "/trigkit4",true);
    xhr.send();
</script>

以上的trigkit4是相对路径,如果我们要使用CORS,相关Ajax代码可能如下所示:

<script type=”text/javascript”> var xhr = new XMLHttpRequest();
xhr.open(“GET”, “);
xhr.send(); </script>

1
2
3
4
5
<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
    xhr.send();
</script>

代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。


要解决跨域的问题,我们可以使用以下几种方法:

准备工作

在开始之前,我们需要给 AngularJS 搭配上一些现代化 webapp
开发套件,以便后面能更方便地装载上 mobx 引擎。

回流(reflow)与重绘(repaint)

这里首先要分清两个概念,重绘与回流。

回流(reflow)

当渲染树(render
Tree)中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow),也就是重新布局(relayout)。

每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

重绘(repaint)

当render
tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如
background-color 。则就叫称为重绘。

值得注意的是,回流必将引起重绘,而重绘不一定会引起回流。

明显,回流的代价更大,简单而言,当操作元素会使元素修改它的大小或位置,那么就会发生回流。

回流何时触发:

  • 调整窗口大小(Resizing the window)
  • 改变字体(Changing the font)
  • 增加或者移除样式表(Adding or removing a stylesheet)
  • 内容变化,比如用户在input框中输入文字(Content changes, such as a
    user typing text in
  • an input box)
  • 激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)(Activation
    of CSS pseudo classes such as :hover (in IE the activation of the
    pseudo class of a sibling))
  • 操作 class 属性(Manipulating the class attribute)
  • 脚本操作 DOM(A script manipulating the DOM)
  • 计算 offsetWidth 和 offsetHeight 属性(Calculating offsetWidth and
    offsetHeight)
  • 设置 style 属性的值 (Setting a property of the style attribute)

所以对于页面而言,我们的宗旨就是尽量减少页面的回流重绘,简单的一个栗子:

CSS

will-change: auto will-change: scroll-position will-change: contents
will-change: transform // Example of <custom-ident> will-change:
opacity // Example of <custom-ident> will-change: left, top //
Example of two <animateable-feature> will-change: unset
will-change: initial will-change: inherit // 示例 .example{ will-change:
transform; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
will-change: auto
will-change: scroll-position
will-change: contents
will-change: transform        // Example of <custom-ident>
will-change: opacity          // Example of <custom-ident>
will-change: left, top        // Example of two <animateable-feature>
will-change: unset
will-change: initial
will-change: inherit
// 示例
.example{
    will-change: transform;
}

上面四句,因为涉及了 offsetHeight
操作,浏览器强制 reflow 了两次,而下面四句合并了 offset
操作,所以减少了一次页面的回流。 

减少回流、重绘其实就是需要减少对渲染树的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。

flush队列

其实浏览器自身是有优化策略的,如果每句 Javascript 都去操作 DOM
使之进行回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护
1
个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会
flush 队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

但是也有例外,因为有的时候我们需要精确获取某些样式信息,下面这些:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight

  • scrollTop/Left/Width/Height

  • clientTop/Left/Width/Height

  • width,height

  • 请求了getComputedStyle(), 或者 IE的 currentStyle

这个时候,浏览器为了反馈最精确的信息,需要立即回流重绘一次,确保给到我们的信息是准确的,所以可能导致
flush 队列提前执行了。

display:none 与 visibility:hidden

两者都可以在页面上隐藏节点。不同之处在于,

  • display:none 隐藏后的元素不占据任何空间。它的宽度、高度等各种属性值都将“丢失”
  • visibility:hidden 隐藏的元素空间依旧存在。它仍具有高度、宽度等属性值

从性能的角度而言,即是回流与重绘的方面,

  • display:none  会触发 reflow(回流)
  • visibility:hidden  只会触发 repaint(重绘),因为没有发现位置变化

他们两者在优化中 visibility:hidden
会显得更好,因为我们不会因为它而去改变了文档中已经定义好的显示层次结构了。

对子元素的影响:

  • display:none 一旦父节点元素应用了
    display:none,父节点及其子孙节点元素全部不可见,而且无论其子孙元素如何设置
    display 值都无法显示;
  • visibility:hidden
    一旦父节点元素应用了 visibility:hidden,则其子孙后代也都会全部不可见。不过存在隐藏“失效”的情况。当其子孙元素应用了
    visibility:visible,那么这个子孙元素又会显现出来。

 

详解JS跨域问题

2016/10/31 · JavaScript
· Javascript,
跨域

原文出处: trigkit4(@trigkit4
)   

AngularJS 配合 ES6/next

现在是2018年,使用 ES6 开发应用已经成为事实标准(有可能的推荐直接上 TS
)。如何将 AngularJS 搭载上 ES6
这里不再赘述,可以看我之前的这篇文章:Angular1.x + ES6
开发风格指南

动画的性能检测及优化

耗性能样式

不同样式在消耗性能方面是不同的,譬如 box-shadow
从渲染角度来讲十分耗性能,原因就是与其他样式相比,它们的绘制代码执行时间过长。这就是说,如果一个耗性能严重的样式经常需要重绘,那么你就会遇到性能问题。其次你要知道,没有不变的事情,在今天性能很差的样式,可能明天就被优化,并且浏览器之间也存在差异。

因此关键在于,你要借助开发工具来分辨出性能瓶颈所在,然后设法减少浏览器的工作量。

好在 chrome
浏览器提供了许多强大的功能,让我们可以检测我们的动画性能,除了上面提到的,我们还可以通过勾选下面这个
show FPS meter 显示页面的 FPS 信息,以及 GPU 的使用率:

图片 3

 

使用 will-change 提高页面滚动、动画等渲染性能

官方文档说,这是一个仍处于实验阶段的功能,所以在未来版本的浏览器中该功能的语法和行为可能随之改变。

图片 4

使用方法示例:(具体每个取值的意义,去翻翻文档)

CSS

will-change: auto will-change: scroll-position will-change: contents
will-change: transform // Example of <custom-ident> will-change:
opacity // Example of <custom-ident> will-change: left, top //
Example of two <animateable-feature> will-change: unset
will-change: initial will-change: inherit // 示例 .example{ will-change:
transform; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
will-change: auto
will-change: scroll-position
will-change: contents
will-change: transform        // Example of <custom-ident>
will-change: opacity          // Example of <custom-ident>
will-change: left, top        // Example of two <animateable-feature>
will-change: unset
will-change: initial
will-change: inherit
// 示例
.example{
    will-change: transform;
}

will-change 为 web
开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。 这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。

值得注意的是,用好这个属性并不是很容易:

  • 不要将 will-change
    应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。

  • 有节制地使用:通常,当元素恢复到初始状态时,浏览器会丢弃掉之前做的优化工作。但是如果直接在样式表中显式声明了 will-change 属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值。

  • 不要过早应用 will-change
    优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题。

  • 给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上 will-change
    属性。

 

使用 transform3d api 代替 transform api,强制开始 GPU 加速

GPU 能够加速 Web 动画,这个上文已经反复提到了。

3D transform
会启用GPU加速,例如 translate3D, scaleZ 之类,当然我们的页面可能并没有
3D 变换,但是不代表我们不能启用 GPU 加速,在非 3D 变换的页面也使用 3D
transform
来操作,算是一种 hack 加速法。我们实际上不需要z轴的变化,但是还是假模假样地声明了,去欺骗浏览器。

参考文献:

  • Rendering: repaint, reflow/relayout,
    restyle
  • Scrolling
    Performance
  • MDN–will-change
  • How (not) to trigger a layout in
    WebKit
  • High Performance
    Animations
  • Accelerated Rendering in
    Chrome
  • CSS3
    制作3D旋转球体

到此本文结束,如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

打赏支持我写出更多好文章,谢谢!

打赏作者

JSONP的优缺点

JSONP的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。

JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

2. 定义 ViewModel

在标准的 MVVM 架构里,ViewModel/Controller
除了构建视图本身的状态数据(即局部状态)外,作为视图跟业务模型之间沟通的桥梁,其主要职责是将业务模型适配(转换/组装)成对视图更友好的数据模型。因此,在
mobx 视角下,ViewModel 主要由以下几部分组成:

  • 视图(局部)状态对应的 observable data
class ViewModel { @observable isLoading = true; @observable
isModelOpened = false; }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f45684167140-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f45684167140-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f45684167140-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f45684167140-7">
7
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f45684167140-1" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f45684167140-2" class="crayon-line crayon-striped-line">
    @observable
</div>
<div id="crayon-5b8f6aab02f45684167140-3" class="crayon-line">
    isLoading = true;
</div>
<div id="crayon-5b8f6aab02f45684167140-4" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f6aab02f45684167140-5" class="crayon-line">
 @observable
</div>
<div id="crayon-5b8f6aab02f45684167140-6" class="crayon-line crayon-striped-line">
 isModelOpened = false;
</div>
<div id="crayon-5b8f6aab02f45684167140-7" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

可观察数据(对应的 observer 为
view),即视图需要对其变化自动做出响应的数据。在 mobx-angularjs
库的协助下,通常 observable data 的变化会使关联的视图自动触发
rerender(或触发网络请求之类的副作用)。ViewModel 中的 observable data
通常是视图状态(UI-State),如 isLoading、isOpened 等。

  • 由 应用/视图 状态衍生的 computed data

    Computed values are values that can be derived from the existing
    state or other computed values.

class ViewModel { @computed get userName() { return
\`${this.user.firstName} ${this.user.lastName}\`; } }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f6aab02f49065322680-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f49065322680-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f49065322680-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f49065322680-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f6aab02f49065322680-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f6aab02f49065322680-6">
6
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f6aab02f49065322680-1" class="crayon-line">
class ViewModel {
</div>
<div id="crayon-5b8f6aab02f49065322680-2" class="crayon-line crayon-striped-line">
    @computed
</div>
<div id="crayon-5b8f6aab02f49065322680-3" class="crayon-line">
    get userName() {
</div>
<div id="crayon-5b8f6aab02f49065322680-4" class="crayon-line crayon-striped-line">
        return `${this.user.firstName} ${this.user.lastName}`;
</div>
<div id="crayon-5b8f6aab02f49065322680-5" class="crayon-line">
    }
</div>
<div id="crayon-5b8f6aab02f49065322680-6" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

计算数据指的是由其他 observable/computed data
转换而来,更方便视图直接使用的衍生数据(derived
data)。 **在重业务轻交互的 web 类应用中(通常是各种企业服务软件),
computed data 在 ViewModel 中应该占主要部分,且基本是由业务 store
中的数据(即应用状态)转换而来。** computed
这种数据推导关系描述能确保我们的应用遵循 single source of truth
原则,不会出现数据不一致的情况,这也是 RP 编程中的基本原则之一。
  • action
    ViewModel 中的 action
    除了一小部分改变视图状态的行为外,大部分应该是直接调用 Model/Store
    中的 action 来完成业务状态的流转。建议把所有对 observable data
    的操作都放到被 aciton 装饰的方法下进行。

mobx 配合下,一个相对完整的 ViewModel 大概长这样:

import UserStore from ‘./UserStore’; class ViewModel {
@inject(UserStore) store; @observable isDropdownOpened = false;
@computed get userName() { return `${this.store.firstName}
${this.store.lastName}`; } <a
href=”;
toggel() { this.isDropdownOpened = !isDropdownOpened; }
updateFirstName(firstName) { this.store.updateFirstName(firstName); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import UserStore from ‘./UserStore’;
  
class ViewModel {
      
    @inject(UserStore)
    store;
    @observable
    isDropdownOpened = false;
 
@computed
get userName() {
     return `${this.store.firstName} ${this.store.lastName}`;
}
  
<a href="http://www.jobbole.com/members/Francesco246437">@action</a>
toggel() {
     this.isDropdownOpened = !isDropdownOpened;
}
      
    updateFirstName(firstName) {
        this.store.updateFirstName(firstName);
    }
}

浏览器渲染展示原理 及 对web动画的影响

小标题起得有点大,我们知道,不同浏览器的内核(渲染引擎,Rendering
Engine)是不一样的,例如现在最主流的 chrome 浏览器的内核是 Blink
内核(在Chrome(28及往后版本)、Opera(15及往后版本)和Yandex浏览器中使用),火狐是
Gecko,IE 是 Trident
,浏览器内核负责对网页语法的解释并渲染(显示)网页,不同浏览器内核的工作原理并不完全一致。

所以其实下面将主要讨论的是 chrome 浏览器下的渲染原理。因为 chrome
内核渲染可查证的资料较多,对于其他内核的浏览器不敢妄下定论,所以下面展开的讨论默认是针对
chrome 浏览器的。

首先,我要抛出一点结论:

使用 transform3d api 代替 transform api,强制开始 GPU 加速

这里谈到了 GPU 加速,为什么 GPU 能够加速 3D
变换?这一切又必须要从浏览器底层的渲染讲起,浏览器渲染展示网页的过程,老生常谈,面试必问,大致分为:

    1. 解析HTML(HTML Parser)
    1. 构建DOM树(DOM Tree)
    1. 渲染树构建(Render Tree)
    1. 绘制渲染树(Painting)

找到了一张很经典的图:

图片 5

这个渲染过程作为一个基础知识,继续往下深入。

当页面加载并解析完毕后,它在浏览器内代表了一个大家十分熟悉的结构:DOM(Document
Object
Model,文档对象模型)。在浏览器渲染一个页面时,它使用了许多没有暴露给开发者的中间表现形式,其中最重要的结构便是层(layer)。

这个层就是本文重点要讨论的内容:

而在 Chrome 中,存在有不同类型的层: RenderLayer(负责 DOM
子树),GraphicsLayer(负责 RenderLayer
的子树)。接下来我们所讨论的将是 GraphicsLayer 层。

GraphicsLayer 层是作为纹理(texture)上传给 GPU 的。

这里这个纹理很重要,那么,

什么是纹理(texture)

这里的纹理指的是 GPU 的一个术语:可以把它想象成一个从主存储器(例如
RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmap
image)。一旦它被移动到 GPU 中,你可以将它匹配成一个网格几何体(mesh
geometry),在 Chrome 中使用纹理来从 GPU
上获得大块的页面内容。通过将纹理应用到一个非常简单的矩形网格就能很容易匹配不同的位置(position)和变形(transformation),这也就是
3D CSS 的工作原理。

说起来很难懂,直接看例子,在 chrome 中,我们是可以看到上文所述的
GraphicsLayer — 层的概念。在开发者工具中,我们进行如下选择调出 show
layer borders 选项:

图片 6

在一个极简单的页面,我们可以看到如下所示,这个页面只有一个层。蓝色网格表示瓦片(tile),你可以把它们当作是层的单元(并不是层),Chrome
可以将它们作为一个大层的部分上传给 GPU:

图片 7

元素自身层的创建

因为上面的页面十分简单,所以并没有产生层,但是在很复杂的页面中,譬如我们给元素设置一个
3D CSS 属性来变换它,我们就能看到当元素拥有自己的层时是什么样子。

注意橘黄色的边框,它画出了该视图中层的轮廓:

图片 8

 

何时触发创建层 ?

上面示意图中黄色边框框住的层,就是 GraphicsLayer ,它对于我们的 Web
动画而言非常重要,通常,Chrome 会将一个层的内容在作为纹理上传到 GPU
前先绘制(paint)进一个位图中。如果内容不会改变,那么就没有必要重绘(repaint)层。

这样做的意义在于:花在重绘上的时间可以用来做别的事情,例如运行
JavaScript,如果绘制的时间很长,还会造成动画的故障与延迟。

那么一个元素什么时候会触发创建一个层?从目前来说,满足以下任意情况便会创建层:

  • 3D 或透视变换(perspective、transform) CSS 属性
  • 使用加速视频解码的 <video> 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 对自己的 opacity 做 CSS 动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index
    较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

层的重绘

对于静态 Web 页面而言,层在第一次被绘制出来之后将不会被改变,但对于 Web
动画,页面的 DOM
元素是在不断变换的,如果层的内容在变换过程中发生了改变,那么层将会被重绘(repaint)。

强大的 chrome
开发者工具提供了工具让我们可以查看到动画页面运行中,哪些内容被重新绘制了:

图片 9

在旧版的 chrome 中,是有 show paint rects
这一个选项的,可以查看页面有哪些层被重绘了,并以红色边框标识出来。

但是新版的 chrome 貌似把这个选项移除了,现在的选项是 enable paint
flashing ,其作用也是标识出网站动态变换的地方,并且以绿色边框标识出来。

看上面的示意图,可以看到页面中有几处绿色的框,表示发生了重绘。注意
Chrome 并不会始终重绘整个层,它会尝试智能的去重绘 DOM 中失效的部分。

按照道理,页面发生这么多动画,重绘应该很频繁才对,但是上图我的行星动画中我只看到了寥寥绿色重绘框,我的个人理解是,一是
GPU 优化,二是如果整个动画页面只有一个层,那么运用了 transform
进行变换,页面必然需要重绘,但是采用分层(GraphicsLayer )技术,也就是上面说符合情况的元素各自创建层,那么一个元素所创建的层运用
transform 变换,譬如 rotate
旋转,这个时候该层的旋转变换并没有影响到其他层,那么该层不一定需要被重绘。(个人之见,还请提出指正)。

了解层的重绘对 Web 动画的性能优化至关重要。

是什么原因导致失效(invalidation)进而强制重绘的呢?这个问题很难详尽回答,因为存在大量导致边界失效的情况。最常见的情况就是通过操作
CSS 样式来修改 DOM 或导致重排。

查找引发重绘和重排根源的最好办法就是使用开发者工具的时间轴和 enable
paint flashing 工具,然后试着找出恰好在重绘/重排前修改了 DOM 的地方。

总结

那么浏览器是如何从 DOM 元素到最终动画的展示呢?

  • 浏览器解析 HTML 获取 DOM 后分割为多个图层(GraphicsLayer)
  • 对每个图层的节点计算样式结果(Recalculate style–样式重计算)
  • 为每个节点生成图形和位置(Layout–回流和重布局)
  • 将每个节点绘制填充到图层位图中(Paint Setup和Paint–重绘)
  • 图层作为纹理(texture)上传至 GPU
  • 符合多个图层到页面上生成最终屏幕图像(Composite Layers–图层重组)

Web
动画很大一部分开销在于层的重绘,以层为基础的复合模型对渲染性能有着深远的影响。当不需要绘制时,复合操作的开销可以忽略不计,因此在试着调试渲染性能问题时,首要目标就是要避免层的重绘。那么这就给动画的性能优化提供了方向,减少元素的重绘与回流。

 

通过修改document.domain来跨子域

浏览器都有一个同源策略,其限制之一就是第一种方法中我们说的不能通过ajax的方法去请求不同源中的文档。
它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。
不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是http://www.example.com/a.html
, 在这个页面里面有一个iframe,它的src是http://example.com/b.html,
很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:

<script type=”text/javascript”> function test(){ var iframe =
document.getElementById(‘ifame’); var win =
document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
var doc = win.document;//这里获取不到iframe里的document对象 var name =
win.name;//这里同样获取不到window对象的name属性 } </script>
<iframe id = “iframe” src=”” onload =
“test()”></iframe>

1
2
3
4
5
6
7
8
9
<script type="text/javascript">
    function test(){
        var iframe = document.getElementById(‘ifame’);
        var win = document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
        var doc = win.document;//这里获取不到iframe里的document对象
        var name = win.name;//这里同样获取不到window对象的name属性
    }
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>

这个时候,document.domain就可以派上用场了,我们只要把http://www.example.com/a.html

http://example.com/b.html这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

1.在页面 http://www.example.com/a.html 中设置document.domain:

<script type=”text/javascript”> function test(){ var iframe =
document.getElementById(‘ifame’); var win =
document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
var doc = win.document;//这里获取不到iframe里的document对象 var name =
win.name;//这里同样获取不到window对象的name属性 } </script>
<iframe id = “iframe” src=”” onload =
“test()”></iframe>

1
2
3
4
5
6
7
8
9
<script type="text/javascript">
    function test(){
        var iframe = document.getElementById(‘ifame’);
        var win = document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
        var doc = win.document;//这里获取不到iframe里的document对象
        var name = win.name;//这里同样获取不到window对象的name属性
    }
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>

2.在页面 http://example.com/b.html 中也设置document.domain:

<script type=”text/javascript”> document.domain =
‘example.com’;//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

1
2
3
<script type="text/javascript">
    document.domain = ‘example.com’;//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

修改document.domain的方法只适用于不同子域的框架间的交互。

mobx-angularjs 加速应用的魔法

从上文的示例代码中我们可以看到,将 mobx 跟 angularjs
衔接运转起来的是 mobx-autorun指令,我们翻下 mobx-angularjs 代码:

const link: angular.IDirectiveLinkFn = ($scope) => { const {
$$watchers = [] } = $scope as any const debouncedDigest =
debounce($scope.$digest.bind($scope), 0); const dispose = reaction( ()
=> […$$watchers].map(watcher => watcher.get($scope)), () =>
!$scope.$root.$$phase && debouncedDigest() ) $scope.$on(‘$destroy’,
dispose) }

1
2
3
4
5
6
7
8
9
10
11
12
const link: angular.IDirectiveLinkFn = ($scope) => {
 
  const { $$watchers = [] } = $scope as any
  const debouncedDigest = debounce($scope.$digest.bind($scope), 0);
 
  const dispose = reaction(
    () => […$$watchers].map(watcher => watcher.get($scope)),
    () => !$scope.$root.$$phase && debouncedDigest()
  )
 
  $scope.$on(‘$destroy’, dispose)
}

可以看到 核心代码 其实就三行:

reaction( () => […$$watchers].map(watcher =>
watcher.get($scope)), () => !$scope.$root.$$phase &&
debouncedDigest()

1
2
3
reaction(
    () => […$$watchers].map(watcher => watcher.get($scope)),
    () => !$scope.$root.$$phase && debouncedDigest()

思路非常简单,即在指令 link 之后,遍历一遍当前 scope 上挂载的 watchers
并取值,由于这个动作是在 mobx reaction 执行上下文中进行的,因此 watcher
里依赖的所有 observable 都会被收集起来,这样当下次其中任何一个
observable 发生变更时,都会触发 reaction 的副作用对 scope 进行
digest,从而达到自动更新视图的目的。

我们知道,angularjs 的性能被广为诟病并不是因为 ‘脏检查’ 本身慢,而是因为
angularjs 在每次异步事件发生时都是无脑的从根节点开始向下
digest,从而会导致一些不必要的 loop 造成的。而当我们在搭载上 mobx 的
push-based 的 change propagation
机制时,只有当被视图真正使用的数据发生变化时,相关联的视图才会触发局部
digest (可以理解为只有 observable data 存在 subscriber/observer
时,状态变化才会触发关联依赖的重算,从而避免不必要资源消耗,即所谓的
lazy)
,区别于异步事件触发即无脑地 $rootScope.$apply,
这种方式显然更高效。

CSS3 3D 行星运转动画 + 浏览器渲染原理

2016/04/29 · CSS ·
动画

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

承接上一篇:《CSS3进阶:酷炫的3D旋转透视》 。

最近入坑 Web 动画,所以把自己的学习过程记录一下分享给大家。

CSS3 3D 行星运转 demo
页面请戳:Demo。(建议使用Chrome打开)

本文完整的代码,以及更多的 CSS3
效果,在我 Github 上可以看到,也希望大家可以点个
star。

嗯,可能有些人打不开 demo
或者页面乱了,贴几张效果图:(图片有点大,耐心等待一会)

CSS3 3D 行星运转效果图

图片 10

随机再截屏了一张:

图片 11

强烈建议你点进
Demo页感受一下
CSS3 3D 的魅力,图片能展现的东西毕竟有限。

然后,这个 CSS3 3D 行星运转动画的制作过程不再详细赘述,本篇的重点放在
Web 动画介绍及性能优化方面。详细的 CSS3 3D
可以回看上一篇博客:《CSS3进阶:酷炫的3D旋转透视》。简单的思路:

  1. 利用上一篇所制作的 3D 照片墙为原型,改造而来;

2.
每一个球体的制作,想了许多方法,最终使用了这种折中的方式,每一个球体本身也是一个
CSS3 3D 图形。然后在制作过程中使用 Sass 编写 CSS 可以减少很多繁琐的编写
CSS 动画的过程;

  1. Demo 当中有使用 Javascript
    写了一个鼠标跟随的监听事件,去掉这个事件,整个行星运动动画本身是纯 CSS
    实现的。

下面将进入本文的重点,从性能优化的角度讲讲浏览器渲染展示原理,浏览器的重绘与重排,动画的性能检测优化等:

 

发表评论

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