react native 中es6语法解析

解构赋值


var {
  StyleSheet,
  Text,
  View
} = React;

这句代码是ES6 中新增的解构(Destructuring)赋值语句。准许你获取对象的多个属性并且使用一条语句将它们赋给多个变量。

上面的代码等价于:


var StyleSheet = React.StyleSheet;
var Text = React.Text;
var View = React.View

再看几个例子,以前,为变量赋值,只能直接指定值:


var a = 1;
var b = 2;
var c = 3;

而ES6 允许这样写:


var [a, b, c] = [1, 2, 3];

更详细的内容可参看: 变量的解构赋值

箭头函数

React Native 里面经常会出现类似的代码:

ES6中新增的箭头操作符 => 简化了函数的书写。操作符左边为输入的参数,而右边则是进行的操作以及返回的值 Inputs=>outputs

举几个栗子感受下:


var array = [1, 2, 3];
//传统写法
array.forEach(function(v, i, a) {
    console.log(v);
});
//ES6
array.forEach(v => console.log(v));

var sum = (num1, num2) => { return num1 + num2; }
//等同于:
var sum = function(num1, num2) {
    return num1 + num2;
 };

更多详细内容请自行Google,或查看: https://www.imququ.com/post/arrow-function-in-es6.html

延展操作符(Spread operator)

这个 … 操作符(也被叫做延展操作符 - spread operator)已经被 ES6 数组 支持。它允许传递数组或者类数组直接做为函数的参数而不用通过apply。


var people=['Wayou','John','Sherlock'];
//sayHello函数本来接收三个单独的参数人妖,人二和人三
function sayHello(people1,people2,people3){
    console.log(`Hello ${people1},${people2},${people3}`);
}
//但是我们将一个数组以拓展参数的形式传递,它能很好地映射到每个单独的参数
sayHello(...people);//输出:Hello Wayou,John,Sherlock 

//而在以前,如果需要传递数组当参数,我们需要使用函数的apply方法
sayHello.apply(null,people);//输出:Hello Wayou,John,Sherlock 

而在 React 中,延展操作符一般用于属性的批量赋值上。在JSX中,可以使用…运算符,表示将一个对象的键值对与ReactElement的props属性合并。


var props = {};
  props.foo = x;
  props.bar = y;
  var component = <Component {...props} />;
  
//等价于
var props = {};
  props.foo = x;
  props.bar = y;
  var component = <Component foo={x} bar={y} />;

它也可以和普通的XML属性混合使用,需要同名属性,后者将覆盖前者:


var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'

更多详细信息: https://facebook.github.io/react/docs/jsx-spread.html

class

ES6中添加了对类的支持,引入了class关键字(其实class在JavaScript中一直是保留字,目的就是考虑到可能在以后的新版本中会用到,现在终于派上用场了)。JS本身就是面向对象的,ES6中提供的类实际上只是JS原型模式的包装。现在提供原生的class支持后,对象的创建,继承更加直观了,并且父类方法的调用,实例化,静态方法和构造函数等概念都更加形象化。


class PropertyView extends Component {
    render() {
        return (
            <View></View>
        )
    }
}

//等价于
var PropertyView = React.createClass({
    render() {
        return (
            <View></View>
        )
    }
})

方法定义(method definition)

ECMAScript 6中,引入了一种名叫方法定义(method definition)的新语法糖,相对于以前的完整写法,这种简写形式可以让你少写一个function键字.


React.createClass({
    render() {
        return (
            <View></View>
        )
    }
})

//等价于

React.createClass({
    render : function() {
        return (
            <View></View>
        )
    }
})

最后,推荐一个ES6的PPT,写得不错: http://khan4019.github.io/ES6/

react-native demo一例

前2周,fackbook发布了react-native , 一时间前端界异常兴奋。各种概念飞满天,俺作为前端一份子,也凑凑热闹。

花了2天时间,边看API边写代码,react-native 真是太新了,好多问题都google不到内容,来来去去都是官方文档,这2天下来,也算摸清了react-native的一些用法。

DEMO地址: https://github.com/hugohua/react-native-demo 以后又可以催逼自己会ios开发,哈哈。

过两天再写点经验贴吧。先贴个readme来凑凑字数。

React Native Demo

如何运行

先确保你已安装好了React Native 所需的依赖环境 
在根目录下执行 npm install 
再执行 npm start 
最后在Xcode中点击run 运行 或者按 command + R 
可能遇到的问题

error 1003 错误

在家开着VPN写代码,一般会遇到该问题,解决方法:

打开项目中的AppDelegate.m,找到这行代码: jsCodeLocation = [NSURLURLWithString:@"http://localhost:8081/index.ios.bundle"], ,将localhost换成自己的ip

一点经验

图片自适应

react native 中,图片必须明确写明大小值,不然无法显示,同时width : ‘100%”,这种写法不支持。

如果需要自适应,有几种做法:

只写高度值,不写宽度值,外层容器使用flex来做好布局,再配合resizeMode实现图片自适应即可。

例子1 :


<View style={{flex : 1,borderRightWidth : 1,borderRightColor: '#eeeeee'}}>
                    <Image style={{height: 110,resizeMode: Image.resizeMode.contain}} source={{uri: 'http://gtms01.alicdn.com/tps/i1/TB1nif8HpXXXXc6XVXXAyLxZVXX-320-188.jpg'}} />
                </View>

例子2 :


<View style={{
	  flex: 1,
	  alignItems: 'stretch',
	}}>
	  <Image ssource={{uri: 'http://gtms01.alicdn.com/tps/i1/TB1nif8HpXXXXc6XVXXAyLxZVXX-320-188.jpg'}} style={{ flex: 1 }} />
	</View>

使用Dimensions来获取设备viewport的宽高


var Dimensions = require('Dimensions');
	var { width, height } = Dimensions.get('window');
	var image = (
	  <Image style={{width: width, height: 100 }} source={{uri: 'http://gtms01.alicdn.com/tps/i1/TB1nif8HpXXXXc6XVXXAyLxZVXX-320-188.jpg'}} />
	);

关于layout-css

react-native(rn)中使用flex来布局,目前使用来看,配合positon : ‘absolute’是能够满足基本页面布局需求的。

但是rn中没有zIndex,也没有position : ‘fixed’,在复杂的页面布局中,会稍微有点麻烦,但还是能实现类似的效果。

rg中只实现了css中很小的一个子集,还有很多属性值无法使用,并且属性写法繁琐,如在web中的css 如果要写padding : 10px 5px 15px 20px,在ng中则全部要分开属性写:paddingTop : 10,paddingRight : 5 … 感觉一夜回到解放前。。

positon : ‘absolute’定位方式是相对于父级元素,不管父级是否具有relative。

缺少一些常用的css属性,如中划线,两端对齐等。

最终效果图

函数内巧用注释实现多行文本拼接

经常会遇到这么个场景,需要将html代码转为JavaScript输出。
一般的做法,就是使用字符串拼接或者使用数组拼接后最终转为字符串。

比如常规做法:

var str = '' +
'<!doctype html>' +
'<html>' +
'   <body>' +
'       <h1>函数内巧用注释实现多行文本拼接 www.ghugo.com</h1>' +
'   </body>' +
'</html>' +
'';

以前,我一般是用转换工具实现,比如这个网址:HTML to JavaScript Convertor

用工具确实是个好方法,那有没有更好的解决方案?以前曾用过函数内用注射来实现本地调试的效果,那么注释是否也可以实现字符串拼接?

于是google了一番,有些收获。

实现方案如下:

function multiline(fn){
		var reCommentContents = /\/\*!?(?:\@preserve)?[ \t]*(?:\r\n|\n)([\s\S]*?)(?:\r\n|\n)[ \t]*\*\//;
	var match = reCommentContents.exec(fn.toString());

	if (!match) {
		throw new TypeError('Multiline comment missing.');
	}

	return match[1];
}

原理非常简单:
1. 在一个function中写上一段多行注释
2. 将此function toString()
3. 将多行注释内容用正则匹配出来

如何使用?

multiline(function(){/*
<!doctype html>
<html>
    <body>
        <h1>函数内巧用注释实现多行文本拼接 www.ghugo.com</h1>
    </body>
</html>
*/});

函数执行后输出:

<!doctype html>
<html>
    <body>
        <h1>函数内巧用注释实现多行文本拼接 www.ghugo.com</h1>
    </body>
</html>

利用注射巧妙的省去了字符串拼接的烦恼,那么在实际项目中是否可用?
一般在实际项目上线前,js都需要压缩,而压缩后将导致正则提取失败。

如何防止注释被压缩工具去掉?

1. uglify: 使用 /*@preserve 代替 /* 即可
2. Closure Compiler(Google): 使用 /*@preserve 代替 /*
3. YUI Compressor: 使用 /*! 代替 /*

如果需要压缩的话,可用如下方式输出:

multiline(function(){/*!@preserve
<!doctype html>
<html>
    <body>
        <h1>函数内巧用注释实现多行文本拼接 www.ghugo.com</h1>
    </body>
</html>
*/});

项目地址fork:https://github.com/hugohua/multiline

再谈mobile web retina 下 1px 边框解决方案

本文实际上想说的是ios8下 1px解决方案。 1px的边框在devicePixelRatio = 2的retina屏下会显示成2px,在iphone 6 plug 下,更显示成3px。由其影响美感。

还好,时代总是进步的。也许很多人都不知道,现在IOS8下,已经支持0.5px了。。 那么意味着,在devicePixelRatio = 2下,我们可以使用如下的css代码:

但是在ios7以下,android等其他系统里,0.5px会被显示为0px,即该解决方案需要写hack兼容老旧系统。

三种方案:

1、JS判断UA,是否是ios8+,是的话则输出类名hairlines,为了防止重绘,这段代码加在head里即可。

if (/iP(hone|od|ad)/.test(navigator.userAgent)) {
	var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/),
		version = parseInt(v[1], 10);
	if(version >= 8){
		document.documentElement.classList.add('hairlines')
	}
}

2、JS判断是否支持0.5px边框,是的话,则输出类名hairlines

if (window.devicePixelRatio &amp;&amp; devicePixelRatio >= 2) {
  var testElem = document.createElement('div');
  testElem.style.border = '.5px solid transparent';
  document.body.appendChild(testElem);
  if (testElem.offsetHeight == 1)
  {
    document.querySelector('html').classList.add('hairlines');
  }
  document.body.removeChild(testElem);
}
// This assumes this script runs in <body>, if it runs in <head> wrap it in $(document).ready(function() {   })

相比于第一种方法,这种方法的可靠性更高一些,但是需要把js放在body标签内,相对来说会有一些重绘,个人建议是用第一种方法。

3、服务端做ios版本判断,输出相应的类名

相比于JS的实现,个人更倾向于在服务端完成,这样前端也少几行代码,并且更加可靠。

如在wormhole里的实现(wormhole是nodejs环境下的一个服务端渲染模版的容器)

{{#if($plugins.detector.os.name === "ios" &amp;&amp; $plugins.detector.os.version >= 8)}}
    {{set (hairlines = "hairlines")}}
{{/if}}
<html class="{{hairlines}}">

加上类名后,就可以针对该类名写相应的css了。比如:

div{border:1px solid #000}
.hairlines div{border-width:0.5px}

也许你会问,那ios7以下和其他android机下怎么搞?我的建议是:还是维持老样,不去处理,随着时间的推移,我相信最终都会支持0.5 和 0.3 px边框的。

如果硬要兼容,怎么整?方案也有很多,稍微介绍下:

1、通过viewport + REM的方式来兼容。

目前这种兼容方案相对比较完美,适合新项目(老项目改用REM单位成本会比较高)。淘宝M首页就是这种方案。

在devicePixelRatio = 2 时,输出viewport

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

在devicePixelRatio = 3 时,输出viewport

<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">

同时通过设置对应viewport的rem基准值,这种方式就可以像以前一样轻松愉快的写1px了。关于REM布局,可以参考下我上一篇文章《移动端H5页面之iphone6的适配》

其他方案(该部分内容来源于妙净同学的分享):

2、 transform: scale(0.5)

实现方式

height:1px;
-webkit-transform: scaleY(0.5);
-webkit-transform-origin:0 0;
overflow: hidden;

优点

圆角无法实现,hack代码多,实现4条边框比较闹心

缺点

只能单独使用,如果嵌套,scale的作用也会对包含的元素产生,不想要的影响,所以此种方案配合:after和:before独立使用较多,比如画一个商品的边框四条线,容器的after和before可以画2条线,利用容器的父元素的after、before再画2条线。

.after-scale{
    position: relative;
}
.after-scale:after{
    content:"";
    position: absolute;
    bottom:0px;
    left:0px;
    right:0px;
    border-bottom:1px solid #c8c7cc;
    -webkit-transform:scaleY(.5);
    -webkit-transform-origin:0 0;
}

3、 box-shadow

实现方式

利用css 对阴影处理的方式实现0.5px的效果

底部一条线

-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5);

优点
基本所有场景都能满足,包含圆角的button,单条,多条线,

缺点

颜色不好处理, 黑色 rgba(0,0,0,1) 最浓的情况了。有阴影出现,不好用。

参考链接

4、 background-image

实现方式

设置1px通过css 实现的image,50%有颜色,50%透明

.border {
        background-image:linear-gradient(180deg, red, red 50%, transparent 50%),
        linear-gradient(270deg, red, red 50%, transparent 50%),
        linear-gradient(0deg, red, red 50%, transparent 50%),
        linear-gradient(90deg, red, red 50%, transparent 50%);
        background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%;
        background-repeat: no-repeat;
        background-position: top, right top,  bottom, left top;
        padding: 10px;
    }

优点

配合background-image,background-size,background-position 可以实现单条,多条边框。边框的颜色随意设置

缺点

如果有圆角的效果,很sorry 圆角的地方没有线框的颜色。都要写的代码也不少

参考链接

5、 用图片

实现方式

.border-image{
    border-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB5JREFUeNpiPnH8zH/G////MzAxAAHTyRNn/wMEGABpvQm9g9TJ1QAAAABJRU5ErkJggg==") 2 0 stretch;
border-width: 0px 0px 1px;
}

优点

缺点

也可以通过修改图片来达到圆角的效果,但是由于图片的原因,压缩过后的图片边缘变模糊了(不放大的情况下不明显),需要引用图片或者base64,边框颜色修改起来不方便。

参考:http://dieulot.net/css-retina-hairline

position:sticky 使用条件分析

哪些浏览器支持,可以查看:http://caniuse.com/#search=sticky

用这个属性,主要是ios下的safari已经支持了,在ios下可以免去js模拟,效果更平滑。

在移动端模拟sticky效果,还是非常蛋疼的,需要同时监听touchmove和scroll事件,页面惯性滚动时会有一定的延迟(scorll事件在滚动停止才触发),同时移动端对fixed定位支持也不大好。JS实现,效果还是大打则扣。

关于这个position:sticky的介绍,可以google一下,网上有很多介绍了,比如神飞的《position:sticky介绍》

今天主要讲的是,position:sticky的使用条件,很多同学经常遇到position:sticky失效的情况,今天对sticky这个属性做了一番调试,总结了一些规律。现分享下。

俗话说,弄不清楚原理,就熟记结论也是一样的,那就先抛结论,sticky满足以下条件才能生效:

1、具有sticky属性的元素,其父级高度必须大于sticky元素的高度。

2、sticky元素的底部,不能和父级底部重叠。(这条不好表述,文后详细说明)

3、sticky元素的父级不能含有overflow:hidden 和 overflow:auto 属性

4、必须具有top,或 bottom 属性。

同时要注意,sticky元素仅在他父级容器内有效,超出容器范围则不再生效了。

先看个正常生效的DEMO:

See the Pen fqdkF by hugo (@baofen14787) on CodePen.

然后在sticky元素外套个div,再看看效果,发现sticky效果失效了。原因是结论的第一点,sticky元素的父级高度刚好等于sticky的高度,所以失效

See the Pen cgHdq by hugo (@baofen14787) on CodePen.

继续看第3个DEMO,也是失效的。虽然父元素的高度大于sticky元素的高度,但是sticky元素的底部刚好也是父元素的最后一个元素。(和父元素的底部重叠了)
实际上,sticky元素仅在他父级容器内有效,而sticky刚好已经到了生效的最边缘了,所以看起来就失效了。

See the Pen lKgtC by hugo (@baofen14787) on CodePen.

继续第4个DEMO,给父级容器加上overflow:hidden,sticky效果又失效了。

See the Pen okgGn by hugo (@baofen14787) on CodePen.

最后再说一下,sticky仅在父元素内生效,看DEMO

See the Pen zqyog by hugo (@baofen14787) on CodePen.

好了,DEMO放完,最后给个作业,请将这个页面sticky效果修复.

See the Pen vamnz by hugo (@baofen14787) on CodePen.

CSS命名之姓氏命名法

背景:

由历史原因及个人习惯引起的DOM结构、命名不统一,导致不同成员在维护同一页面时,无从下手,效率低下,迭代、维护成本极高。

命名原则:

1.概述

1.1基于姓氏命名法(继承+外来),如下图:

1.2说明:

(1).简单模块:在子孙模块数量可预测的情况下,继承祖先模块的命名前缀

如上图的焦点图轮播模块,模块整体结构比较简单,子元素的内容相对较少,这种情况下,假设将模块命名为 : slider,那么其后续的所有子元素,均需要继承这个类名。

slider
├── slider-list
|    |
|    └── ....
└── slider-nav
|    |
|    └── ....

(2).复杂模块:当子孙模块数量较多,且无法预估时,可以选择采用继承“祖先+父”模块的命名前缀,以保证模块之间的独立性

在上图的模块中,一个大楼层里面包含一个子模块:tab切换。在这种结构复杂的模块中,为了保证模块类名使用简单(css起名也是一个体力活)和结构看起来更清晰,可以使用继承“祖先+父”模块的命名前缀的方式。

在这个楼层模块中,楼层的名字叫:floor ,里面的tab切换单个内容块名字叫 item,那么 tab切换内部的子元素命名规则为 : floor-item-xxx

如:

floor
├── floor-hd
|    |
|    ├── floor-tit
|
├── floor-item
|    |
|    ├── floor-item-tit
|    └── floor-item-desc

上面代码中使用了2次 tit 来表示标题,也就是说如果你自己有一组命名包,你就可以很轻松的进行命名了,比如描述相关:desc、info、extra等等。

(3).复合模块:子模块中,可以嵌套其他模块,可理解为“娶媳妇”

模块间本身会有嵌套情况,如上面的结构,在今日特惠这个模块中嵌套了一个公共组件。可以理解为娶媳妇。(媳妇一般是外姓的)。

如:

hotsales
├── hotsales_li
| |
| └── mod_goods
| |
| ├── mod_goods_price
| └── mod_goods_tit
| └── mod_goods_promo

当然,其实第二点也可以用第三点的方式来实现,那么这里就涉及到一个问题?什么时候用第三点提到的方法。

比较推荐的建议是,如果嵌套的模块是一个比较通用的组件,那么可以用第三点,将子模块抽出成一个独立的组件,而其他情况下建议使用第二点的方式。

基于以上的方式,可以完成基本的命名规则,但是无法区分模块的类型。(模块是否可复用?复用范围?)

于是我们在模块的命名规则上遵循以下原则:

  1. 全站公共模块以”mod-”开头
  2. 页面公共模块以”xx-mod-”开头(xx为页面名称缩写)
  3. 独立模块,命名为一个简短的单词,如”hot、floor、banner” 等

基于以下2点来区分全站公共模块及页面模块。

  1. 同一个页面出现2次及以上同一个类名
  2. 2个以上页面出现同一个类名

在这种规则下,可能会出现这个模块原先是独立模块的,然后发现可以复用了,然后就修改类名变成公共模块。这种变更成本比较低,可接受。(因为独立模块在页面上只有一处,修改类名不会影响到其他样式)

基于这个css姓氏命名法,我们的类名是长命名的方式了,只要保证了最外层模块的唯一性,那么里面的子模块类名是不可能会出现重复的。
于是,我们要求CSS以单层命名为主,嵌套一般不能操过3层。

这样在修改样式与html结构时,相互之间是耦合的,css不依赖于html的结构。(我知道很多同学写less或scss喜欢像html那样一层一层嵌套)

最后附上以前feeling写的代码给大家参考:

【译】chrome – GPU加速的图层组合操作

原文:http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome

译注:(专业词汇对应翻译)

Node(节点)
DOM Tree(DOM树)
RenderObject(渲染对象)
RenderTree(渲染树)
GraphicsContext(图形环境对象、图形上下文对象)
一个Graphics Context默示一个绘制目标。它包含绘制体系用于完成绘制指令的绘制参数和设备相干信息。Graphics Context定义了根蒂根基的绘制属性,如色彩、裁减区域、线条宽度和样式信息、字体信息、混淆模式等。来自 <http://www.byywee.com/page/M0/S772/772925.html>
Bitmap位图
RenderLayers渲染图层
Alpha mask透明遮罩
Css filtercss滤镜
Software Rendering path软件渲染模式。译成“软件渲染路径”、“软件渲染路线”都可以,它是webkit渲染页面时的传统模式,不依赖硬件加速。
HWND返回窗体或控件的句柄(注意 OLE容器控件不支持该属性。句柄:是由操作环境定义的一个唯一的整数值,它被程序用来标识或者切换到对象,如窗体或控件等。)来自 <http://baike.baidu.com/link?url=knrqRLDl2voMqg6N6pq97dWrZGaxwR7G7gwW4HQyHzn6WGTflGg0nnms9afq7oARZ1hDaduE-81jnuxXQaGziq>
IPCIPC(Inter-Process Communication,进程间通信) 来自 <http://baike.baidu.com/view/373.htm>

概要

本文档阐述chrome浏览器“硬件加速图层组合”的背景和实现细节。

介绍

一般情况下,web浏览器完全依赖CPU进行网页内容的渲染。随着GPU在硬件设备(即便是很小的硬设)中的集成应用,以及富媒体例如视频、3D效果等在网页体验中扮演着越来越重要的角色,人们开始注意想方设法高效利用底层硬件获取更好的性能、电源节能。诚然,利用GPU直接对网页内容进行图层组合可以带来显著的性能提升(译注:这里的性能应该是指渲染速度方面)。最明显的例子莫过于video元素,它利用硬件进行视频解码,利用WebGL为H5 canvas提供硬件3D加速图形绘制渲染,期间占用的内存区域是CPU无法快速存取的。(译注:WebGL参考百科文档)

将网页图层组合的操作扔给GPU还有别的诸多好处,在大多数情况下,GPU在涉及大量像素的图形绘制、组合操作方面比CPU快的多,因为GPU是专门设计善干这类事情的硬件。此外,充分利用GPU做图形绘制、组合这类工作的同时,CPU就有更多的可以并行地做其他方面的运算,这种并行工作的方式创建了一个高效的图形管道。

Part 1  Webkit渲染基础及软件渲染模式

Webkit渲染引擎的源码巨大且复杂,晦涩难懂,几乎没啥米注释!为了弄清楚GPU在chrome里面如何工作,我们得先了解下webkit渲染网页的基本构件流程。下面我们从webkit如何工作作为基本出发点,回头再慢慢引入GPU影响渲染的过程,最终了解GPU如何工作。

节点和DOM树

在Webkit里面,网页内容内部存储为一个节点对象的树,我们称之为DOM树。网页上的每个标签包括标签之间的文本,各自对应DOM树上的一个节点。DOM树最上面的节点即Document节点。

从节点到渲染对象

DOM树里面的每个视觉可见的节点,均有一个相对应的渲染对象。在webkit内,渲染对象被存储成树形数据结构,和DOM树并行,我们称之为渲染树。渲染对象知道如何在显示介质(译注:例如显示器)上呈现(绘制)对应DOM树节点的内容,具体的方式是调用图形环境对象的绘制指令。而图形环境对象负责将像素写入到位图中去,最终被显示器显示出来。在chrome里面,图形环境对象封装了一个名为Skia的2D绘图库,于是大部分绘制指令被委托给SkCanvas或者SkPlatformCanvas执行。(关于chrome如何使用Skia库,可以参考这篇文档

在软件渲染模式里,整个页面只有一个图形环境对象,所有的渲染对象被绘制到这个共享的图形环境对象中。

从渲染对象到渲染图层

每个渲染对象通过其父级渲染对象直接或间接地关联一个渲染图层。

共享相同坐标空间的渲染对象(如被相同css变形影响的哪些对象)一般情况下属于同一个渲染图层。渲染图层的存在,使得页面的元素可以按正确的顺序进行组合,使得重叠的元素、半透明的元素等等得以恰当的渲染显示。触发webkit为渲染对象创建新的渲染图层的条件有很多,具体可见源码,在方法RenderBoxModelObject::requiresLayer()或者该方法在其他类的重载方法里可以窥其一二。总的来说,对于渲染对象,只要满足下面情形之一,均会致使新的渲染图层的创建。

  1. 它是页面的根对象。(译注:应该就是document节点对应的渲染对象)
  2. 它有显式的css 位置属性。(relative,absolute,或transform)
  3. 它是透明的
  4. 它有overflow,透明遮罩或阴影(反光)
  5. 它有css滤镜
  6. 它对应的节点是3D环境或2D加速环境的canvas元素
  7. 它对应的节点是video元素

值得注意的是,渲染对象和渲染图层并不是1对1的关系。一个渲染对象所关联的渲染图层,要么是为它自身创建的渲染图层,要么是它父级对象所关联的渲染图层(如果有多个有渲染图层的父对象,取最近的那个)。(译注:同一个树路径上的多个渲染对象可能会关联同一个渲染图层)。

渲染对象是树形结构,显然渲染图层也会是树形的。渲染图层树的根节点对应页面的根元素,每个图层节点的下行图层节点(子孙节点)对应该图层视觉包含的图层(译注:视觉包含不一定是dom树上的父子、祖孙节点)。每个图层节点的子图层节点(译注:是子节点不是子孙节点)被分类存储于两个升序排序的队列(negZOrderList和posZOrderList)中,队列negZOrderList包含z轴次序比当前图层低的图层(因而这类图层在渲染图层树的位置在当前图层下面),posZOrderList反之亦然。

凑到一起:我们有很多的树

总结下前面的内容,从概念上来看我们一共有3棵并行的树结构,各自在渲染过程中的作用不尽相同:

  1. DOM树(Dom Tree),是我们最基本的模型
  2.  渲染对象树 (RenderObject Tree),其节点和DOM树的可见节点是一对一的关系。渲染过程中,渲染对象知道如果绘制其对应的Dom节点。
  3.  渲染图层树(RenderLayer Tree),由渲染图层构成。其节点和渲染对象树的节点是1对多的关系,因为一个渲染对象关联的渲染图层要么是它自身的,要么是它父级对象的。渲染图层树体现了图层之间的z轴顺序。

软件渲染模式

从根本上来说,webkit渲染页面的时候,是从根图层节点开始,进而逐级递归遍历渲染图层树。对于渲染页面的过程,Webkit的代码库里有两种迥异的编码实现过程,一个是软件渲染过程,另一个是硬件加速的渲染过程,前者是传统的渲染模式。

在软件渲染模式中,网页渲染的时候被按照渲染图层树从上而下进行,(译注:图层树体现了图层之间的z轴顺序,所以从上而下实则为从里到外,z轴越小越先被渲染。另外这里的z轴顺序和css里面的z-index有关系但并没有对应关系的,css里面的z-index是元素的,这里指的是渲染图层的。)图层被渲染的复杂过程可以参考下源码里面的RenderLayer::paintLayer()方法,为简明起见下面从这个方法里面简化提取了几个基本的步骤:

  1. 如果图层有效性校验失败,直接退出该方法。【译注:原文是图层与已坏矩形区域(the damage rect,没上下文不知道怎么翻译这玩意)发生交错,则及早退出该方法】
  2. 递归调用该图层的negZOrderList队列中的子图层的paintLayer()方法
  3. 请求该图层关联的渲染对象去绘制自身。【译注:别忘了前面提到的,渲染对象知道如何在显示介质上绘制其对应的dom节点的内容】
  4. 这个请求渲染对象绘制自身的过程,是从创建了该渲染图层的那个渲染对象开始,依据渲染对象树,对其子节点进行递归遍历绘制处理。一旦发现某个渲染对象所关联的渲染图层不是当前被渲染的图层,则停止递归遍历绘制处理。
  5. 递归调用该图层的posZOrderList队列中的子图层的paintLayer()方法

在这个模式中,渲染对象调用一个共享的图形环境对象的绘制方法,将自身绘制到目标位图中,最终被显示器显示出来。【译注:可回头看看前面的“从节点到渲染对象”小节】

要注意的是,图形环境对象本身并没有图层的概念,为了正确绘制半透明效果的图层,需要做些特别的处理:半透明效果的渲染图层在请求其关联的渲染对象绘制自身之前,会先调用图形环境对象的beginTransparencyLayer()方法。在Skia图形库实现的图形环境对象中,调用beginTransparencyLayer()方法,到导致接下来相关的渲染对象绘制自身至新的位图中,当这些渲染对象绘制完毕后,会调用一个对应的endTransparencyLayer()方法。最后,beginTransparencyLayer方法内创建的位图对象,会和paintLayer()方法创建的位图对象组合在一起,最终被显示器显示出来。

【译注:半透明效果的渲染图层,其渲染绘制的过程大概是这样的:paintLayer() => beginTransparencyLayer() => 调用关联的渲染对象的绘制方法 => endTransparenceLayer(),paintLayer和beginTransparencyLayer都会导致在内存中创建位图对象,最后两个位图对象组合在一起最终显示出来。】

从Webkit到显示器

当所有的渲染图层被绘制到一个共享位图之后,该位图需要在显示器上被显示出来。在chrome里面,位图存储于共享内存中,经由IPC(进程间通讯)传输至浏览器进程。浏览器进程负责调用操作系统的视窗API(例如windows操作系统中的HWND),将位图绘制到网页对应的标签页或窗口中去。

backbone.marionette 资料整理

部分API说明:

上面的中文API转自 http://www.ituring.com.cn/article/31580

官方文档资料: https://github.com/marionettejs/backbone.marionette/tree/master/docs

网上的一些例子,有些文章了,可能和现有版本多少有些差异,但影响不大,对于了解marionette的用法有一定帮助:

1、http://www.smashingmagazine.com/2013/02/11/introduction-backbone-marionette/

2、这里有很多实用的例子可以参考,重点看看那个TODOMVC的那个例子:https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette

3、与requireJs相结合:https://github.com/jsoverson/todomvc/tree/master/labs/dependency-examples/backbone_marionette_require

如果不熟requireJs的话 可以先看看中文API文档: http://makingmobile.org/docs/tools/requirejs-api-zh/

4、整理成PDF的文档: marionette-gentle-introduction-sample

php通过正则提取页面相关信息

1、获取页面标题

<?php
//提取标题
preg_match('/<title>(?<title>.*?)<\/title>/i', $html, $titleArr);
$title = $titleArr['title'];
?>

2、获取body主体内容,并将背景图片提取出来替换成其他图片地址

/**
 * 获取BODY主体区域内容
 * @param $html
 * @param $urlRoot
 * @return mixed
 */
function getBody($html,$urlRoot = null){
	//提取BODY主体
	preg_match('/<!--body-->(.*?)<!--body-->/is ', $html, $bodyArr);
	if(!$bodyArr){
		preg_match('/<body.*?>(.*?)<\/body>/is ', $html, $bodyArr);
	}
	$body = $bodyArr[1];
	//替换img文件
	$body =  preg_replace('/(<[img|IMG].*src=[\'|"])(\.\.\/)*(img.[^\'||^"]+)/',"$1$urlRoot$3",$body);
	//替换html文件内的css背景图片
	$body =  preg_replace('~\b(background(-image)?\s*:(.*?)\(\s*[\'|"]?)(\.\.\/)*(img.*?)?\s*\)~i',"$1$urlRoot$5)",$body);
	return $body;
}

3、提取页面Description内容

function getDescription($html){
	// Get the 'content' attribute value in a <meta name="description" ... />
	$matches = array();

	// Search for <meta name="description" content="Buy my stuff" />
	preg_match('/<meta.*?name=("|\')description("|\').*?content=("|\')(.*?)("|\')/i', $html, $matches);
	if (count($matches) > 4) {
		return trim($matches[4]);
	}

	// Order of attributes could be swapped around: <meta content="Buy my stuff" name="description" />
	preg_match('/<meta.*?content=("|\')(.*?)("|\').*?name=("|\')description("|\')/i', $html, $matches);
	if (count($matches) > 2) {
		return trim($matches[2]);
	}

	// No match
	return null;
}

4、替换css文件的背景图片地址

/**
 * 获取CSS内容
 * @param $cssCnt
 * @param $urlRoot
 * @return mixed
 */
function getCss($cssCnt,$urlRoot =null){
	//匹配包含 img文件夹的相对路径图片 (含义绝对路径的不包含在其中)
	//匹配替换不一定准确,因为只是将 含义 ../ 的地址转为url 而没有考虑 ../../ 之类的层级关系
	$css =  preg_replace('~\b(background(-image)?\s*:(.*?)\(\s*[\'|"]?)(\.\.\/)*(img.*?)?\s*\)~i',"$1$urlRoot$5)",$cssCnt);
	//添加css前缀
	$css =  preg_replace('/\b.(.*?)[,|{]/',"pat .$0",$cssCnt);
	//TODO 压缩css
	return $css;
}