一分钟预览,移动端自适应方案

致我们终将组件化的Web

2015/11/25 · HTML5 · 1
评论 ·
组件化

原文出处:
AlloyTeam   

这篇文章将从两年前的一次技术争论开始。争论的聚焦就是下图的两个目录分层结构。我说按模块划分好,他说你傻逼啊,当然是按资源划分。

图片 1 《=》图片 2

”按模块划分“目录结构,把当前模块下的所有逻辑和资源都放一起了,这对于多人独自开发和维护个人模块不是很好吗?当然了,那争论的结果是我乖乖地改回主流的”按资源划分“的目录结构。因为,没有做到JS模块化和资源模块化,仅仅物理位置上的模块划分是没有意义的,只会增加构建的成本而已。

虽然他说得好有道理我无言以对,但是我心不甘,等待他日前端组件化成熟了,再来一战!

而今天就是我重申正义的日子!只是当年那个跟你撕逼的人不在。

模块化的不足

模块一般指能够独立拆分且通用的代码单元。由于JavaScript语言本身没有内置的模块机制(ES6有了!!),我们一般会使用CMD或ADM建立起模块机制。现在大部分稍微大型一点的项目,都会使用requirejs或者seajs来实现JS的模块化。多人分工合作开发,其各自定义依赖和暴露接口,维护功能模块间独立性,对于项目的开发效率和项目后期扩展和维护,都是是有很大的帮助作用。

但,麻烦大家稍微略读一下下面的代码

JavaScript

require([
‘Tmpl!../tmpl/list.html’,’lib/qqapi’,’module/position’,’module/refresh’,’module/page’,’module/net’
], function(listTmpl, QQapi, Position, Refresh, Page, NET){ var foo =
”, bar = []; QQapi.report(); Position.getLocaiton(function(data){
//… }); var init = function(){ bind();
NET.get(‘/cgi-bin/xxx/xxx’,function(data){ renderA(data.banner);
renderB(data.list); }); }; var processData = function(){ }; var bind =
function(){ }; var renderA = function(){ }; var renderB =
function(data){ listTmpl.render(‘#listContent’,processData(data)); };
var refresh = function(){ Page.refresh(); }; // app start init(); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require([
    ‘Tmpl!../tmpl/list.html’,’lib/qqapi’,’module/position’,’module/refresh’,’module/page’,’module/net’
], function(listTmpl, QQapi, Position, Refresh, Page, NET){
    var foo = ”,
        bar = [];
    QQapi.report();
    Position.getLocaiton(function(data){
        //…
    });
    var init = function(){
        bind();
        NET.get(‘/cgi-bin/xxx/xxx’,function(data){
            renderA(data.banner);
            renderB(data.list);
        });
    };
    var processData = function(){
    };
    var bind = function(){
    };
    var renderA = function(){
    };
    var renderB = function(data){
        listTmpl.render(‘#listContent’,processData(data));
    };
    var refresh = function(){
        Page.refresh();
    };
    // app start
    init();
});

上面是具体某个页面的主js,已经封装了像Position,NET,Refresh等功能模块,但页面的主逻辑依旧是”面向过程“的代码结构。所谓面向过程,是指根据页面的渲染过程来编写代码结构。像:init
-> getData -> processData -> bindevent -> report -> xxx

方法之间线性跳转,你大概也能感受这样代码弊端。随着页面逻辑越来越复杂,这条”过程线“也会越来越长,并且越来越绕。加之缺少规范约束,其他项目成员根据各自需要,在”过程线“加插各自逻辑,最终这个页面的逻辑变得难以维护。

图片 3

开发需要小心翼翼,生怕影响“过程线”后面正常逻辑。并且每一次加插或修改都是bug泛滥,无不令产品相关人员个个提心吊胆。

 页面结构模块化

基于上面的面向过程的问题,行业内也有不少解决方案,而我们团队也总结出一套成熟的解决方案:Abstractjs,页面结构模块化。我们可以把我们的页面想象为一个乐高机器人,需要不同零件组装,如下图,假设页面划分为tabContainer,listContainer和imgsContainer三个模块。最终把这些模块add到最终的pageModel里面,最终使用rock方法让页面启动起来。

图片 4
(原过程线示例图)

图片 5
(页面结构化示例图)

下面是伪代码的实现

JavaScript

require([
‘Tmpl!../tmpl/list.html’,’Tmpl!../tmpl/imgs.html’,’lib/qqapi’,’module/refresh’,’module/page’
], function(listTmpl, imgsTmpl, QQapi, Refresh, Page ){ var
tabContainer = new RenderModel({ renderContainer: ‘#tabWrap’, data: {},
renderTmpl: “<li soda-repeat=’item in
data.tabs’>{{item}}</li>”, event: function(){ // tab’s event }
}); var listContainer = new ScrollModel({ scrollEl: $.os.ios ?
$(‘#Page’) : window, renderContainer: ‘#listWrap’, renderTmpl:
listTmpl, cgiName: ‘/cgi-bin/index-list?num=1’, processData:
function(data) { //… }, event: function(){ // listElement’s event },
error: function(data) { Page.show(‘数据返回异常[‘ + data.retcode +
‘]’); } }); var imgsContainer = new renderModel({ renderContainer:
‘#imgsWrap’, renderTmpl: listTmpl, cgiName: ‘/cgi-bin/getPics’,
processData: function(data) { //… }, event: function(){ //
imgsElement’s event }, complete: function(data) { QQapi.report(); } });
var page = new PageModel();
page.add([tabContainer,listContainer,imgsContainer]); page.rock(); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
require([
    ‘Tmpl!../tmpl/list.html’,’Tmpl!../tmpl/imgs.html’,’lib/qqapi’,’module/refresh’,’module/page’
], function(listTmpl, imgsTmpl, QQapi, Refresh, Page ){
 
    var tabContainer = new RenderModel({
        renderContainer: ‘#tabWrap’,
        data: {},
        renderTmpl: "<li soda-repeat=’item in data.tabs’>{{item}}</li>",
        event: function(){
            // tab’s event
        }
    });
 
    var listContainer = new ScrollModel({
        scrollEl: $.os.ios ? $(‘#Page’) : window,
        renderContainer: ‘#listWrap’,
        renderTmpl: listTmpl,
        cgiName: ‘/cgi-bin/index-list?num=1’,
        processData: function(data) {
            //…
        },
        event: function(){
            // listElement’s event
        },
        error: function(data) {
            Page.show(‘数据返回异常[‘ + data.retcode + ‘]’);
        }
    });
 
    var imgsContainer = new renderModel({
        renderContainer: ‘#imgsWrap’,
        renderTmpl: listTmpl,
        cgiName: ‘/cgi-bin/getPics’,
        processData: function(data) {
            //…
        },
        event: function(){
            // imgsElement’s event
        },
        complete: function(data) {
           QQapi.report();
        }
    });
 
    var page = new PageModel();
    page.add([tabContainer,listContainer,imgsContainer]);
    page.rock();
 
});

我们把这些常用的请求CGI,处理数据,事件绑定,上报,容错处理等一系列逻辑方法,以页面块为单位封装成一个Model模块。

这样的一个抽象层Model,我们可以清晰地看到该页面块,请求的CGI是什么,绑定了什么事件,做了什么上报,出错怎么处理。新增的代码就应该放置在相应的模块上相应的状态方法(preload,process,event,complete…),杜绝了以往的无规则乱增代码的行文。并且,根据不同业务逻辑封装不同类型的Model,如列表滚动的ScrollModel,滑块功能的SliderModel等等,可以进行高度封装,集中优化。

现在基于Model的页面结构开发,已经带有一点”组件化“的味道。每个Model都带有各自的数据,模板,逻辑。已经算是一个完整的功能单元。但距离真正的WebComponent还是有一段距离,至少满足不了我的”理想目录结构“。

 WebComponents 标准

我们回顾一下使用一个datapicker的jquery的插件,所需要的步奏:

  1. 引入插件js

  2. 引入插件所需的css(如果有)

  3. copy 组件的所需的html片段

  4. 添加代码触发组件启动

现阶段的“组件”基本上只能达到是某个功能单元上的集合。他的资源都是松散地分散在三种资源文件中,而且组件作用域暴露在全局作用域下,缺乏内聚性很容易就会跟其他组件产生冲突,如最简单的css命名冲突。对于这种“组件”,还不如上面的页面结构模块化。

于是W3C按耐不住了,制定一个WebComponents标准,为组件化的未来指引了明路。

下面以较为简洁的方式介绍这份标准,力求大家能够快速了解实现组件化的内容。(对这部分了解的同学,可以跳过这一小节)

1. <template>模板能力

模板这东西大家最熟悉不过了,前些年见的较多的模板性能大战artTemplate,juicer,tmpl,underscoretemplate等等。而现在又有mustachejs无逻辑模板引擎等新入选手。可是大家有没有想过,这么基础的能力,原生HTML5是不支持的(T_T)。

而今天WebComponent将要提供原生的模板能力

XHTML

<template id=”datapcikerTmpl”>
<div>我是原生的模板</div> </template>

1
2
3
<template id="datapcikerTmpl">
<div>我是原生的模板</div>
</template>

template标签内定义了myTmpl的模板,需要使用的时候就要innerHTML= document.querySelector('#myTmpl').content;可以看出这个原生的模板够原始,模板占位符等功能都没有,对于动态数据渲染模板能力只能自力更新。

2. ShadowDom 封装组件独立的内部结构

ShadowDom可以理解为一份有独立作用域的html片段。这些html片段的CSS环境和主文档隔离的,各自保持内部的独立性。也正是ShadowDom的独立特性,使得组件化成为了可能。

JavaScript

var wrap = document.querySelector(‘#wrap’); var shadow =
wrap.createShadowRoot(); shadow.innerHTML = ‘<p>you can not see me
</p>’

1
2
3
var wrap = document.querySelector(‘#wrap’);
var shadow = wrap.createShadowRoot();
shadow.innerHTML = ‘<p>you can not see me </p>’

在具体dom节点上使用createShadowRoot方法即可生成其ShadowDom。就像在整份Html的屋子里面,新建了一个shadow的房间。房间外的人都不知道房间内有什么,保持shadowDom的独立性。

3. 自定义原生标签

初次接触Angularjs的directive指令功能,设定好组件的逻辑后,一个<Datepicker
/>就能引入整个组件。如此狂炫酷炸碉堡天的功能,实在令人拍手称快,跃地三尺。

JavaScript

var tmpl = document.querySelector(‘#datapickerTmpl’); var
datapickerProto = Object.create(HTMLElement.prototype); //
设置把我们模板内容我们的shadowDom datapickerProto.createdCallback =
function() { var root = this.createShadowRoot();
root.appendChild(document.importNode(tmpl.content, true)); }; var
datapicker = docuemnt.registerElement(‘datapicker’,{ prototype:
datapickerProto });

1
2
3
4
5
6
7
8
9
10
11
12
var tmpl = document.querySelector(‘#datapickerTmpl’);
var datapickerProto = Object.create(HTMLElement.prototype);
 
// 设置把我们模板内容我们的shadowDom
datapickerProto.createdCallback = function() {
    var root = this.createShadowRoot();
    root.appendChild(document.importNode(tmpl.content, true));
};
 
var datapicker = docuemnt.registerElement(‘datapicker’,{
    prototype: datapickerProto
});

Object.create方式继承HTMLElement.prototype,得到一个新的prototype。当解析器发现我们在文档中标记它将检查是否一个名为createdCallback的方法。如果找到这个方法它将立即运行它,所以我们把克隆模板的内容来创建的ShadowDom。

最后,registerElement的方法传递我们的prototype来注册自定义标签。

上面的代码开始略显复杂了,把前面两个能力“模板”“shadowDom”结合,形成组件的内部逻辑。最后通过registerElement的方式注册组件。之后可以愉快地<datapicker></datapicker>的使用。

4. imports解决组件间的依赖

XHTML

<link rel=”import” href=”datapciker.html”>

1
<link rel="import" href="datapciker.html">

这个类php最常用的html导入功能,HTML原生也能支持了。

WebComponents标准内容大概到这里,是的,我这里没有什么Demo,也没有实践经验分享。由于webComponents新特性,基本上除了高版本的Chrome支持外,其他浏览器的支持度甚少。虽然有polymer帮忙推动webcompoents的库存在,但是polymer自身的要求版本也是非常高(IE10+)。所以今天的主角并不是他。

我们简单来回顾一下WebCompoents的四部分功能:

1 .<template>定义组件的HTML模板能力

  1. Shadow Dom封装组件的内部结构,并且保持其独立性

  2. Custom Element 对外提供组件的标签,实现自定义标签

  3. import解决组件结合和依赖加载

 组件化实践方案

官方的标准看完了,我们思考一下。一份真正成熟可靠的组件化方案,需要具备的能力。

“资源高内聚”—— 组件资源内部高内聚,组件资源由自身加载控制

“作用域独立”—— 内部结构密封,不与全局或其他组件产生影响

“自定义标签”—— 定义组件的使用方式

“可相互组合”—— 组件正在强大的地方,组件间组装整合

“接口规范化”—— 组件接口有统一规范,或者是生命周期的管理

个人认为,模板能力是基础能力,跟是否组件化没有强联系,所以没有提出一个大点。

既然是实践,现阶段WebComponent的支持度还不成熟,不能作为方案的手段。而另外一套以高性能虚拟Dom为切入点的组件框架React,在facebook的造势下,社区得到了大力发展。另外一名主角Webpack,负责解决组件资源内聚,同时跟React极度切合形成互补。

所以【Webpack】+【React】将会是这套方案的核心技术。

不知道你现在是“又是react+webpack”感到失望图片 6,还是“太好了是react+webpack”不用再学一次新框架的高兴图片 7。无论如何下面的内容不会让你失望的。

一,组件生命周期

图片 8

React天生就是强制性组件化的,所以可以从根本性上解决面向过程代码所带来的麻烦。React组件自身有生命周期方法,能够满足“接口规范化”能力点。并且跟“页面结构模块化”的所封装抽离的几个方法能一一对应。另外react的jsx自带模板功能,把html页面片直接写在render方法内,组件内聚性更加紧密。

由于React编写的JSX是会先生成虚拟Dom的,需要时机才真正插入到Dom树。使用React必须要清楚组件的生命周期,其生命周期三个状态:

Mount: 插入Dom

Update: 更新Dom

Unmount: 拔出Dom

mount这单词翻译增加,嵌入等。我倒是建议“插入”更好理解。插入!拔出!插入!拔出!默念三次,懂了没?别少看黄段子的力量,

图片 9

组件状态就是: 插入-> 更新 ->拔出。

然后每个组件状态会有两种处理函数,一前一后,will函数和did函数。

componentWillMount()  准备插入前

componentDidlMount()  插入后

componentWillUpdate() 准备更新前

componentDidUpdate()  更新后

componentWillUnmount() 准备拔出前

因为拔出后基本都是贤者形态(我说的是组件),所以没有DidUnmount这个方法。

另外React另外一个核心:数据模型props和state,对应着也有自个状态方法

getInitialState()     获取初始化state。

getDefaultProps() 获取默认props。对于那些没有父组件传递的props,通过该方法设置默认的props

componentWillReceiveProps()  已插入的组件收到新的props时调用

还有一个特殊状态的处理函数,用于优化处理

shouldComponentUpdate():判断组件是否需要update调用

加上最重要的render方法,React自身带的方法刚刚好10个。对于初学者来说是比较难以消化。但其实getInitialStatecomponentDidMountrender三个状态方法都能完成大部分组件,不必望而却步。

回到组件化的主题。

一个页面结构模块化的组件,能独立封装整个组件的过程线

图片 10

我们换算成React生命周期方法:

图片 11

 

组件的状态方法流中,有两点需要特殊说明:

1,二次渲染:

由于React的虚拟Dom特性,组件的render函数不需自己触发,根据props和state的改变自个通过差异算法,得出最优的渲染。

请求CGI一般都是异步,所以必定带来二次渲染。只是空数据渲染的时候,有可能会被React优化掉。当数据回来,通过setState,触发二次render

 

2,componentWiillMount与componentDidMount的差别

和大多数React的教程文章不一样,ajax请求我建议在WillMount的方法内执行,而不是组件初始化成功之后的DidMount。这样能在“空数据渲染”阶段之前请求数据,尽早地减少二次渲染的时间。

willMount只会执行一次,非常适合做init的事情。

didMount也只会执行一次,并且这时候真实的Dom已经形成,非常适合事件绑定和complete类的逻辑。

 

 二,JSX很丑,但是组件内聚的关键!

WebComponents的标准之一,需要模板能力。本是以为是我们熟悉的模板能力,但React中的JSX这样的怪胎还是令人议论纷纷。React还没有火起来的时候,大家就已经在微博上狠狠地吐槽了“JSX写的代码这TM的丑”。这其实只是Demo阶段JSX,等到实战的大型项目中的JSX,包含多状态多数据多事件的时候,你会发现………….JSX写的代码还是很丑。

图片 12
(即使用sublime-babel等插件高亮,逻辑和渲染耦合一起,阅读性还是略差)

为什么我们会觉得丑?因为我们早已经对“视图-样式-逻辑”分离的做法潜移默化。

基于维护性和可读性,甚至性能,我们都不建议直接在Dom上面绑定事件或者直接写style属性。我们会在JS写事件代理,在CSS上写上classname,html上的就是清晰的Dom结构。我们很好地维护着MVC的设计模式,一切安好。直到JSX把他们都糅合在一起,所守护的技术栈受到侵略,难免有所抵制。

 

但是从组件化的目的来看,这种高内聚的做法未尝不可。

下面的代码,之前的“逻辑视图分离”模式,我们需要去找相应的js文件,相应的event函数体内,找到td-info的class所绑定的事件。

对比起JSX的高度内聚,所有事件逻辑就是在本身jsx文件内,绑定的就是自身的showInfo方法。组件化的特性能立马体现出来。

(注意:虽然写法上我们好像是HTML的内联事件处理器,但是在React底层并没有实际赋值类似onClick属性,内层还是使用类似事件代理的方式,高效地维护着事件处理器)

再来看一段style的jsx。其实jsx没有对样式有硬性规定,我们完全可遵循之前的定义class的逻辑。任何一段样式都应该用class来定义。在jsx你也完全可以这样做。但是出于组件的独立性,我建议一些只有“一次性”的样式直接使用style赋值更好。减少冗余的class。

XHTML

<div className=”list” style={{background: “#ddd”}}> {list_html}
</div>

1
2
3
<div className="list" style={{background: "#ddd"}}>
   {list_html}
</div>

或许JSX内部有负责繁琐的逻辑样式,可JSX的自定义标签能力,组件的黑盒性立马能体验出来,是不是瞬间美好了很多。

JavaScript

render: function(){ return ( <div> <Menus
bannerNums={this.state.list.length}></Menus> <TableList
data={this.state.list}></TableList> </div> ); }

1
2
3
4
5
6
7
8
render: function(){
    return (
      <div>
         <Menus bannerNums={this.state.list.length}></Menus>
         <TableList data={this.state.list}></TableList>
      </div>
   );
}

虽然JSX本质上是为了虚拟Dom而准备的,但这种逻辑和视图高度合一对于组件化未尝不是一件好事。

 

学习完React这个组件化框架后,看看组件化能力点的完成情况

“资源高内聚”—— (33%)  html与js内聚

“作用域独立”—— (50%)  js的作用域独立

“自定义标签”—— (100%)jsx

“可相互组合”—— (50%)  可组合,但缺乏有效的加载方式

“接口规范化”—— (100%)组件生命周期方法

 

Webpack 资源组件化

对于组件化的资源独立性,一般的模块加载工具和构建流程视乎变得吃力。组件化的构建工程化,不再是之前我们常见的,css合二,js合三,而是体验在组件间的依赖于加载关系。webpack正好符合需求点,一方面填补组件化能力点,另一方帮助我们完善组件化的整体构建环境。

首先要申明一点是,webpack是一个模块加载打包工具,用于管理你的模块资源依赖打包问题。这跟我们熟悉的requirejs模块加载工具,和grunt/gulp构建工具的概念,多多少少有些出入又有些雷同。

图片 13

首先webpak对于CommonJS与AMD同时支持,满足我们模块/组件的加载方式。

JavaScript

require(“module”); require(“../file.js”); exports.doStuff = function()
{}; module.exports = someValue;

1
2
3
4
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;

JavaScript

define(“mymodule”, [“dep1”, “dep2”], function(d1, d2) { return
someExportedValue; });

1
2
3
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
    return someExportedValue;
});

当然最强大的,最突出的,当然是模块打包功能。这正是这一功能,补充了组件化资源依赖,以及整体工程化的能力

根据webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,可以把想css,图片等资源等有依赖关系的“模块”加载。这跟我们使用requirejs这种仅仅处理js大大不同。而这套加载机制,通过一个个loader来实现。

 

JavaScript

// webpack.config.js module.exports = { entry: { entry: ‘./index.jsx’,
}, output: { path: __dirname, filename: ‘[name].min.js’ }, module:
{ loaders: [ {test: /\.css$/, loader: ‘style!css’ }, {test:
/\.(jsx|js)?$/, loader: ‘jsx?harmony’, exclude: /node_modules/},
{test: /\.(png|jpg|jpeg)$/, loader: ‘url-loader?limit=10240’} ] } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// webpack.config.js
module.exports = {
    entry: {
     entry: ‘./index.jsx’,
    },
    output: {
        path: __dirname,
        filename: ‘[name].min.js’
    },
    module: {
        loaders: [
            {test: /\.css$/, loader: ‘style!css’ },
            {test: /\.(jsx|js)?$/, loader: ‘jsx?harmony’, exclude: /node_modules/},
            {test: /\.(png|jpg|jpeg)$/, loader: ‘url-loader?limit=10240’}
        ]
    }
};

上面一份简单的webpack配置文件,留意loaders的配置,数组内一个object配置为一种模块资源的加载机制。test的正则为匹配文件规则,loader的为匹配到文件将由什么加载器处理,多个处理器之间用分隔,处理顺序从右到左。

 

style!css,css文件通过css-loader(处理css),再到style-loader(inline到html)的加工处理流。

jsx文件通过jsx-loader编译,‘?’开启加载参数,harmony支持ES6的语法。

图片资源通过url-loader加载器,配置参数limit,控制少于10KB的图片将会base64化。

 资源文件如何被require?

JavaScript

// 加载组件自身css require(‘./slider.css’); // 加载组件依赖的模块 var
Clip = require(‘./clipitem.js’); // 加载图片资源 var spinnerImg =
require(‘./loading.png’);

1
2
3
4
5
6
// 加载组件自身css
require(‘./slider.css’);
// 加载组件依赖的模块
var Clip = require(‘./clipitem.js’);
// 加载图片资源
var spinnerImg = require(‘./loading.png’);

在webpack的js文件中我们除了require我们正常的js文件,css和png等静态文件也可以被require进来。我们通过webpack命令,编译之后,看看输出结果如何:

JavaScript

webpackJsonp([0], { /* 0 */ /***/ function(module, exports,
__webpack_require__) { // 加载组件自身css
__webpack_require__(1); // 加载组件依赖的模块 var Clip =
__webpack_require__(5); // 加载图片资源 var spinnerImg =
__webpack_require__(6); /***/ }, /* 1 */ /***/
function(module, exports, __webpack_require__) { /***/ }, /* 2
*/ /***/ function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(3)();
exports.push([module.id, “.slider-wrap{\r\n position: relative;\r\n
width: 100%;\r\n margin: 50px;\r\n background:
#fff;\r\n}\r\n\r\n.slider-wrap li{\r\n text-align:
center;\r\n line-height: 20px;\r\n}”, “”]); /***/ }, /* 3 */
/***/ function(module, exports) { /***/ }, /* 4 */ /***/
function(module, exports, __webpack_require__) { /***/ }, /* 5
*/ /***/ function(module, exports) { console.log(‘hello, here is
clipitem.js’) ; /***/ }, /* 6 */ /***/ function(module, exports)
{ module.exports = “data:image/png;base64,iVBORw0KGg……” /***/ }
]);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
webpackJsonp([0], {
/* 0 */
/***/ function(module, exports, __webpack_require__) {
          // 加载组件自身css
          __webpack_require__(1);
          // 加载组件依赖的模块
          var Clip = __webpack_require__(5);
          // 加载图片资源
          var spinnerImg = __webpack_require__(6);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
 
/***/ },
/* 2 */
/***/ function(module, exports, __webpack_require__) {
          exports = module.exports = __webpack_require__(3)();
          exports.push([module.id, ".slider-wrap{\r\n position: relative;\r\n width: 100%;\r\n margin: 50px;\r\n background: #fff;\r\n}\r\n\r\n.slider-wrap li{\r\n text-align: center;\r\n line-height: 20px;\r\n}", ""]);
 
/***/ },
/* 3 */
/***/ function(module, exports) {
 
/***/ },
 
/* 4 */
/***/ function(module, exports, __webpack_require__) {
/***/ },
 
/* 5 */
/***/ function(module, exports) {
          console.log(‘hello, here is clipitem.js’) ;
/***/ },
/* 6 */
/***/ function(module, exports) {
          module.exports = "data:image/png;base64,iVBORw0KGg……"
/***/ }
]);

webpack编译之后,输出文件视乎乱糟糟的,但其实每一个资源都被封装在一个函数体内,并且以编号的形式标记(注释)。这些模块,由webpack的__webpack_require__内部方法加载。入口文件为编号0的函数index.js,可以看到__webpack_require__加载其他编号的模块。

css文件在编号1,由于使用css-loader和style-loader,编号1-4都是处理css。其中编号2我们可以看我们的css的string体。最终会以内联的方式插入到html中。

图片文件在编号6,可以看出exports出base64化的图片。

 组件一体输出

JavaScript

// 加载组件自身css require(‘./slider.css’); // 加载组件依赖的模块 var
React = require(‘react’); var Clip = require(‘../ui/clipitem.jsx’); //
加载图片资源 var spinnerImg = require(‘./loading.png’); var Slider =
React.createClass({ getInitialState: function() { // … },
componentDidMount: function(){ // … }, render: function() { return (
<div> <Clip data={this.props.imgs} /> <img
className=”loading” src={spinnerImg} /> </div> ); } });
module.exports = Slider;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 加载组件自身css
require(‘./slider.css’);
// 加载组件依赖的模块
var React = require(‘react’);
var Clip = require(‘../ui/clipitem.jsx’);
// 加载图片资源
var spinnerImg = require(‘./loading.png’);
var Slider = React.createClass({
    getInitialState: function() {
        // …
    },
    componentDidMount: function(){
        // …
    },
    render: function() {
        return (
            <div>
               <Clip data={this.props.imgs} />
               <img className="loading" src={spinnerImg} />
            </div>
        );
    }
});
module.exports = Slider;

如果说,react使到html和js合为一体。

那么加上webpack,两者结合一起的话。js,css,png(base64),html
所有web资源都能合成一个JS文件。这正是这套方案的核心所在:组件独立一体化。如果要引用一个组件,仅仅require('./slider.js') 即可完成。

 

加入webpack的模块加载器之后,我们组件的加载问题,内聚问题也都成功地解决掉

“资源高内聚”—— (100%) 所有资源可以一js输出

“可相互组合”—— (100%)  可组合可依赖加载

 

 CSS模块化实践

很高兴,你能阅读到这里。目前我们的组件完成度非常的高,资源内聚,易于组合,作用域独立互不污染。。。。等等图片 14,视乎CSS模块的完成度有欠缺。

那么目前组件完成度来看,CSS作用域其实是全局性的,并非组件内部独立。下一步,我们要做得就是如何让我们组件内部的CSS作用域独立。

这时可能有人立马跳出,大喊一句“德玛西亚!”,哦不,应该是“用sass啊傻逼!”。可是项目组件化之后,组件的内部封装已经很好了,其内部dom结构和css趋向简单,独立,甚至是破碎的。LESS和SASS的一体式样式框架的设计,他的嵌套,变量,include,函数等丰富的功能对于整体大型项目的样式管理非常有效。但对于一个功能单一组件内部样式,视乎就变的有点格格不入。“不能为了框架而框架,合适才是最好的”。视乎原生的css能力已经满足组件的样式需求,唯独就是上面的css作用域问题。

 

这里我给出思考的方案:
classname随便写,保持原生的方式。编译阶段,根据组件在项目路径的唯一性,由【组件classname+组件唯一路径】打成md5,生成全局唯一性classname。正当我要写一个loader实现我的想法的时候,发现歪果仁已经早在先走一步了。。。。

这里具体方案参考我之前博客的译文:

之前我们讨论过JS的模块。现在通过Webpack被加载的CSS资源叫做“CSS模块”?我觉得还是有问题的。现在style-loader插件的实现本质上只是创建link[rel=stylesheet]元素插入到document中。这种行为和通常引入JS模块非常不同。引入另一个JS模块是调用它所提供的接口,但引入一个CSS却并不“调用”CSS。所以引入CSS本身对于JS程序来说并不存在“模块化”意义,纯粹只是表达了一种资源依赖——即该组件所要完成的功能还需要某些asset。

因此,那位歪果仁还扩展了“CSS模块化”的概念,除了上面的我们需要局部作用域外,还有很多功能,这里不详述。具体参考原文 

非常赞的一点,就是cssmodules已经被css-loader收纳。所以我们不需要依赖额外的loader,基本的css-loader开启参数modules即可

JavaScript

//webpack.config.js … module: { loaders: [ {test: /\.css$/, loader:
‘style!css?modules&localIdentName=[local]__[name]_[hash:base64:5]’
}, ] } ….

1
2
3
4
5
6
7
8
//webpack.config.js
…  
    module: {
        loaders: [
            {test: /\.css$/, loader: ‘style!css?modules&localIdentName=[local]__[name]_[hash:base64:5]’ },
        ]  
    }
….

modules参数代表开启css-modules功能,loaclIdentName为设置我们编译后的css名字,为了方便debug,我们把classname(local)和组件名字(name)输出。当然可以在最后输出的版本为了节省提交,仅仅使用hash值即可。另外在react中的用法大概如下。

JavaScript

var styles = require(‘./banner.css’); var Banner = new
React.createClass({ … render: function(){ return ( <div> <div
className={styles.classA}></div> </div> ) } });

1
2
3
4
5
6
7
8
9
10
11
var styles = require(‘./banner.css’);
var Banner = new React.createClass({
    …
    render: function(){
        return (
            <div>
                <div className={styles.classA}></div>
            </div>
        )
    }
});

最后这里关于出于对CSS一些思考,

关于css-modules的其它功能,我并不打算使用。在内部分享【我们竭尽所能地让CSS变得复杂】中提及:

我们项目中大部分的CSS都不会像boostrap那样需要变量来设置,身为一线开发者的我们大概能够感受到:设计师们改版UI,绝对不是简单的换个色或改个间距,而是面目全非的全新UI,这绝对不是一个变量所能解决的”维护性“。

反而项目实战过程中,真正要解决的是:在版本迭代过程中那些淘汰掉的过期CSS,大量地堆积在项目当中。我们像极了家中的欧巴酱不舍得丢掉没用的东西,因为这可是我们使用sass或less编写出具有高度的可维护性的,肯定有复用的一天。

这些堆积的过期CSS(or
sass)之间又有部分依赖,一部分过期没用了,一部分又被新的样式复用了,导致没人敢动那些历史样式。结果现网项目迭代还带着大量两年前没用的样式文件。

组件化之后,css的格局同样被革新了。可能postcss才是你现在手上最适合的工具,而不在是sass。

 

到这里,我们终于把组件化最后一个问题也解决了。

“作用域独立”—— (100%) 如同shadowDom作用域独立

 

到这里,我们可以开一瓶82年的雪碧,好好庆祝一下。不是吗?

图片 15

 

 组件化之路还在继续

webpack和react还有很多新非常重要的特性和功能,介于本文仅仅围绕着组件化的为核心,没有一一阐述。另外,配搭gulp/grunt补充webpack构建能力,webpack的codeSplitting,react的组件通信问题,开发与生产环境配置等等,都是整套大型项目方案的所必须的,限于篇幅问题。可以等等我更新下篇,或大家可以自行查阅。

但是,不得不再安利一下react-hotloader神器。热加载的开发模式绝对是下一代前端开发必备。严格说,如果没有了热加载,我会很果断地放弃这套方案,即使这套方案再怎么优秀,我都讨厌react需要5~6s的编译时间。但是hotloader可以在我不刷新页面的情况下,动态修改代码,而且不单单是样式,连逻辑也是即时生效。

图片 16

如上在form表单内。使用热加载,表单不需要重新填写,修改submit的逻辑立刻生效。这样的开发效率真不是提高仅仅一个档次。必须安利一下。

 

或许你发现,使用组件化方案之后,整个技术栈都被更新了一番。学习成本也不少,并且可以预知到,基于组件化的前端还会很多不足的问题,例如性能优化方案需要重新思考,甚至最基本的组件可复用性不一定高。后面很长一段时间,需要我们不断磨练与优化,探求最优的前端组件化之道。

至少我们可以想象,不再担心自己写的代码跟某个谁谁冲突,不再为找某段逻辑在多个文件和方法间穿梭,不再copy一片片逻辑然后改改。我们每次编写都是可重用,可组合,独立且内聚的组件。而每个页面将会由一个个嵌套组合的组件,相互独立却相互作用。

 

对于这样的前端未来,有所期待,不是很好吗

至此,感谢你的阅读。

1 赞 6 收藏 1
评论

图片 17

真实的1px

这一条和设计稿密切想关,要讨论它不能抛开设计稿不谈。

这里先补一下切图课,如果自己要做1x , 2x, 3x 的设计稿。如何去实现?

尺寸!!!

大多数情况下,设计师产出各种尺寸的稿子(事实上一般只是2倍稿子),都是先画出大尺寸的稿子,再去缩小尺寸,最后导出。
这样会带来问题:

如果设计师在2倍稿子里画了一条1px的线,这时候假如我们要在scale=1.0里呈现的话,就会变成0.5px,如下图。

图片 18

而很大一部分手机是无法画出0.5px的,因此这里一般有一个hack

CSS

transform:scaleX(0.5)或transform:scaleY(0.5)

1
transform:scaleX(0.5)或transform:scaleY(0.5)

但是有人提出了,
既然可以改变viewport的scale达到合理利用不同倍屏的优势,为什么不这么写呢。

XHTML

<meta name=”viewport”
content=”initial-scale=2.0,width=device-width/>

1
<meta name="viewport" content="initial-scale=2.0,width=device-width/>

等等,为了设计稿的尺寸我们如此大费周章?

事实上,即使2x设计稿避免了1px。3x设计稿也可能出现2px。

而且这里如果写死scale可能造成部分地方和稿子出入较大,无法还原设计稿,界面的显示会打折扣。

解决这个问题的关键在于:交流

  • 如果你的设计师是个要求严格,而且产品界面把控非常严格的话,应该动态去实现viewport或使用scale的hack去改变。
  • 如果部分区域实在没有必要[ 过度优化 ], scale=1.0
    实在是非常低成本还原的方案,未尝不可。

HTTP/2 源自 SPDY/2

SPDY 系列协议由谷歌开发,于 2009 年公开。它的设计目标是降低 50%
的页面加载时间。当下很多著名的互联网公司都在自己的网站或 APP 中采用了
SPDY 系列协议(当前最新版本是
SPDY/3.1),因为它对性能的提升是显而易见的。主流的浏览器(谷歌、火狐、Opera)也都早已经支持
SPDY,它已经成为了工业标准,HTTP Working-Group 最终决定以 SPDY/2
为基础,开发 HTTP/2。HTTP/2标准于2015年5月以RFC 7540正式发表。

但是,HTTP/2 跟 SPDY 仍有不同的地方,主要是以下两点:

HTTP/2 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
HTTP/2 消息头的压缩算法采用 HPACK ,而非 SPDY 采用的 DEFLATE(感谢网友
逸风之狐指正)

协议文档请见:rfc7540:HTTP2

移动端自适应方案

2015/09/14 ·
JavaScript,
基础技术 ·
移动端,
自适应

原文出处:
大搜车前端团队博客   

前方依旧高能 ^_^ , 本文主要解决以下问题:

  • 真的需要动态生成viewport吗?
  • 如何自适应?

然后给出主观的最佳实践。

  • 最帅的flex

赶时间戳这里传送门

比较无聊干燥的文章,看前请喝水。

研究样本

  1. 手淘 ml.js
  2. 天猫首页
  3. 手机携程

一个月前去了css开发者大会,听到了手淘的自适应方案,想起之前一直就想了解ml.js到底干了什么事。回来仔细研究了一下,抱着好奇心一并看了同样类型的网站的方案,深入学习一下。

研究结论

  1. 手淘

    • 获取手机dpr(window.devicePixelRatio),动态生成viewport。
    • 换取手机宽度,分成10份,每一份的宽度即是rem的尺寸。
    • 根据设计稿尺寸(px)通过计算,转换成rem去布局。

    ps:海外淘宝并没有这样做,而是scale1.0并且图片大概都是2倍图。

  2. 天猫

    • 采用scale=1.0 写死viewport。
    • flex布局,笃定认为布局尺寸是375 (iPhone6)
    • rem 确定非flex的元素
  3. 手机携程
    • 采用scale=1.0 写死viewport
    • px + 百分比布局

实现之前

提及实现之前,先简单过一些概念。

完美视口

完美视口的概念已经街知巷闻了,如果不知道可以先戳这里。

在这几篇文章里,还会学会设备像素,css像素等概念,大神讲的很透彻,这里就不献丑了。

ppk 谈
viewport其1 ppk 谈
viewport其2 ppk 谈
viewport其3

这里给出完美视口

XHTML

<meta name=”viewport”
content=”initial-scale=1.0,width=device-width,user-scalable=0,maximum-scale=1.0″/>

1
<meta name="viewport" content="initial-scale=1.0,width=device-width,user-scalable=0,maximum-scale=1.0"/>

在移动端,低端无定制的需求,都可以用这个完美视口完成。然而看到这篇文章的你,显然完美视口还不能满足。

dpr

dpr是devicePixelRatio的简写,也就是屏幕分辩比

历史原因,由于苹果retina的产生,使得清晰度提升,主要是因为`设备像素`提升了一倍,因此可以用更多像素去绘画更清晰的图像。#我乱说的#

1
历史原因,由于苹果retina的产生,使得清晰度提升,主要是因为`设备像素`提升了一倍,因此可以用更多像素去绘画更清晰的图像。#我乱说的#

坊间对于dpr更通俗的说法叫

  • 一倍屏
  • 两倍屏
  • 三倍屏

scale

scale是屏幕拉伸比。也就是视口上的initial-scale , maximum-sacle 等属性。

scale 和 dpr的关系是倒数。

1
scale 和 dpr的关系是倒数。

直观感受

这是我对dpr的直观感受图片 19

同样去展示 1 x 1 像素的点,虽然在屏幕上看到的大小是一样,但背后表现它的像素数量是不同。

这也意味着,在一样大小的面积内,更多物理像素的屏幕上展现色彩的能力越强。

但这不是我要关注的点,我们关注的是。

1. 是否需要根据倍屏去切换scale达到伸缩的目的

2. 切换scale的成本和回报

下面根据几个实验来回答这两个问题。

自适应问题

实验1 - 传说中的1px

大多数给出要动态切换scale的理由有以下两个。

  1. 1px并不是 [ 真实的1px ] , 2.
    为了充分利用屏幕的分辨率,使用符合屏幕的图片。
1
2
3
1. 1px并不是 [ 真实的1px ] ,
 
2. 为了充分利用屏幕的分辨率,使用符合屏幕的图片。

参考资料

  1. Turn-on HTTP/2 today!
  2. Hypertext Transfer Protocol Version 2
    (HTTP/2)
  3. npm spdy
  4. npm spdy push
  5. How to create a self-signed SSL
    Certificate
  6. HPACK: Header Compression for
    HTTP/2
  7. 用Node.js创建自签名的HTTPS服务器

    1 赞 收藏
    评论

图片 17

对应倍图

对于这一点,争议较多,因为如果要做到对应倍图的话,意味着图片都需要做三份。成本太高了。

这里通常有两种做法

  1. 图片服务

    例如在100×100的图片容器中。

1倍图 http:// img.xxx.com/abc.jpg\_100x100 2倍图 http://
img.xxx.com/abc.jpg\_200x200 3倍图 http://
img.xxx.com/abc.jpg\_300x300

<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-5b8f19d520d5d723297616-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d5d723297616-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d5d723297616-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d5d723297616-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d5d723297616-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d5d723297616-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d5d723297616-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d5d723297616-8">
8
</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-5b8f19d520d5d723297616-1" class="crayon-line">
1倍图
</div>
<div id="crayon-5b8f19d520d5d723297616-2" class="crayon-line crayon-striped-line">
 http:// img.xxx.com/abc.jpg_100x100
</div>
<div id="crayon-5b8f19d520d5d723297616-3" class="crayon-line">
 
</div>
<div id="crayon-5b8f19d520d5d723297616-4" class="crayon-line crayon-striped-line">
 2倍图
</div>
<div id="crayon-5b8f19d520d5d723297616-5" class="crayon-line">
 http:// img.xxx.com/abc.jpg_200x200
</div>
<div id="crayon-5b8f19d520d5d723297616-6" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f19d520d5d723297616-7" class="crayon-line">
 3倍图
</div>
<div id="crayon-5b8f19d520d5d723297616-8" class="crayon-line crayon-striped-line">
 http:// img.xxx.com/abc.jpg_300x300
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 定死尺寸

    放弃1屏手机,全部启用2倍图,由于流量会消耗比较大(低端机),因此滚动加载等优化手段就会显得比较重要了。

实验1 – scale对倍图重要吗

这里看一下不同scale下图片的差异。

  • 测试样本:160×160凯尔特人队标logo(一不小心暴露了绿色的血液)
  • 测试容器:160×160 img标签
  • 测试环境: intial-scale分别为1.0 / 0.5 / 0.3333
  • 图片尺寸: 1x(160×160) 2x(320×320) 3x(480×480)

图片 21

测试结论:不同scale下使用不同图片差异非常大。

但是这里需要验证,是否不同scale同一图片差异起到绝对作用。

图片 22

  • 肉眼观看基本无区别,除了用取色器去获取,会发现有色差和部分像素被分割(下面会说到),之外,用不同scale显示同一图片基本没有什么区别。

实验2 – DownSampling

由于上一个实验最后的图片,使用同一scale下,不同倍数的图片,存在色差,这里验证一下。

  • 测试方案

    测试图片:

 图片 23

图片尺寸: 400×300 , 300×225 , 200×150 , 100×75

测试环境: scale = 1.0

测试容器: 100×75的 img元素

由于之前知道了DownSampling概念的存在,这里只是好奇心驱动试验一下。(对自适应其实没有卵用)

DownSampling是说大图放入比图片尺寸小的容器中的时候,出现像素分割成就近色的情况。

测试结果:

图片 24

注:6plus貌似和其他机型不同。

触发情况: 不同颜色像素接触的地方,会出现DownSampling。

图片 25

rem

对于rem要说的不多,看这张图。对于用到px的元素,使用rem统一去管理是很灵活的!

图片 26

字体

无论是采用动态生成viewport或者写死scale,字体都需要适配大屏。之前提出的rem方案被证实在不同手机上显示不一致,这里还是回归成了px。

px最好用双数

两种方案(这里不考虑媒体查询,因为Android碎..,嗯,不说了…)

  1. JS动态计算(常见做法)
根据不同屏幕宽度计算不同字号大小。 1.
定基准值,设计稿是750宽度(2倍屏),字体的大小是24px. 2.
计算指定宽度的字体大小。 var fontSize = width / 750 \* 24 ;

<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-5b8f19d520d62124238623-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d62124238623-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d62124238623-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d62124238623-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d62124238623-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d62124238623-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d62124238623-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-5b8f19d520d62124238623-1" class="crayon-line">
根据不同屏幕宽度计算不同字号大小。
</div>
<div id="crayon-5b8f19d520d62124238623-2" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f19d520d62124238623-3" class="crayon-line">
1. 定基准值,设计稿是750宽度(2倍屏),字体的大小是24px.
</div>
<div id="crayon-5b8f19d520d62124238623-4" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f19d520d62124238623-5" class="crayon-line">
2. 计算指定宽度的字体大小。
</div>
<div id="crayon-5b8f19d520d62124238623-6" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f19d520d62124238623-7" class="crayon-line">
var fontSize = width / 750 * 24 ;
</div>
</div></td>
</tr>
</tbody>
</table>
  1. 根据dpr设定 (比较好的做法)

    ps : 一般时初始化时设置为根元素html的attribute,

JavaScript

window.document.documentElement.setAttribute('dpr',window.devicePixelRatio)

<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-5b8f19d520d65248160001-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d65248160001-2">
2
</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-5b8f19d520d65248160001-1" class="crayon-line">
   window.document.documentElement.setAttribute('dpr',window.devicePixelRatio)
</div>
<div id="crayon-5b8f19d520d65248160001-2" class="crayon-line crayon-striped-line">
 
</div>
</div></td>
</tr>
</tbody>
</table>

然后css这样写



CSS

\[dpr=1\] { font-size=16px; } \[dpr=2\] { font-size=32px; }

<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-5b8f19d520d69092077898-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d69092077898-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d69092077898-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d69092077898-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d69092077898-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f19d520d69092077898-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f19d520d69092077898-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-5b8f19d520d69092077898-1" class="crayon-line">
[dpr=1] {
</div>
<div id="crayon-5b8f19d520d69092077898-2" class="crayon-line crayon-striped-line">
       font-size=16px; 
</div>
<div id="crayon-5b8f19d520d69092077898-3" class="crayon-line">
}
</div>
<div id="crayon-5b8f19d520d69092077898-4" class="crayon-line crayon-striped-line">
 
</div>
<div id="crayon-5b8f19d520d69092077898-5" class="crayon-line">
[dpr=2] {
</div>
<div id="crayon-5b8f19d520d69092077898-6" class="crayon-line crayon-striped-line">
       font-size=32px; 
</div>
<div id="crayon-5b8f19d520d69092077898-7" class="crayon-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

布局

权衡之下,我觉得flex真的灵活方便太多,因此这里给出一个布局demo。大致如下图。(画的比较粗糙..)

(上稿下还原)

图片 27图片 28

基本涵盖:

  • 固定头部
  • 固定底部
  • 多列自适应
  • 高度自定义
  • 内容滚动

为什么flex能够做到百分比做不到的自适应。

比如我们也去学天猫,笃定认为宽度就是375(iPhone6尺寸),那么两个元素flex分别为200和175。

无需计算百分比,在不同的界面上就会自动计算,而且以该浏览器可以识别的最小单位实现,比自己计算的百分比要精准。

图片 29

demo传送门

结论

  1. 写死initial-scale=1.0 对于实现1px问题,
    问题比较大。与设计师沟通协商才是最好的解决问题的方法。
  2. 写死initial-scale=1.0 对于不同图片的显示,
    采用不同倍图的话,会有一定压缩,但在可接受范围内。(当然,动态生成scale能够完美呈现…)
  3. 布局

    如果采用动态生成viewport方案,就用到rem来还原设计稿(还有rem-px的计算)。成本在效率上。

    如果采用写死initial-scale=1.0方案,就用flex布局,主要成本在flex兼容性上,但是实现非常灵活简单。

后记

viewport的scale的重要性远比我想象的要低很多,我原本以为这就是自适应。

但是后来发现,其实自适应还是回到了远古时代的百分比%,只是现在有更聪明更灵活的方式flex,未来应该有两个方向去自适应。

  • 一个是拥抱vw,vh。(手淘的ml.js十等分宽度,1rem=10vw
  • 一个是更好的使用flex

现在使用后者已经有很多的库可以解决兼容性了,如参考资源最后的一个flex库。

调研的网站并不多,但是百分比仍然是很多人的首选。

参考资源

手淘ml库

手机淘宝

天猫首页

移动端高清、多平适配方案

rem对webapp带来的影响

flex方案 适配到IE10+

 

 

2 赞 10 收藏
评论

图片 17

背景

近年来,http网络请求量日益添加,以下是httparchive统计,从2012-11-01到2016-09-01的请求数量和传输大小的趋势图:

图片 31

 

当前大部份客户端&服务端架构的应用程序,都是用http/1.1连接的,现代浏览器与单个域最大连接数,都在4-6个左右,由上图Total
Requests数据,如果不用CDN分流,平均有20个左右的串行请求。
HTTP2
是1999年发布http1.1后的一次重大的改进,在协议层面改善了以上问题,减少资源占用,来,直接感受一下差异:

HTTP/2 is the future of the Web, and it is
here!
这是 Akamai 公司建立的一个官方的演示,用以说明 HTTP/2 相比于之前的
HTTP/1.1 在性能上的大幅度提升。 同时请求 379 张图片,从Load time
的对比可以看出 HTTP/2 在速度上的优势。

图片 32

 

本文所有源码和抓包文件在github

node中启用http2

node中可以用spdy模块来启动应用,spdy的api,与https是一致的且主流浏览器只支持HTTP/2
Over TLS,需要配置 私钥和证书,本地自签名服务器配置可参考引用6,7

JavaScript

const express = require(‘express’); const fs = require(‘fs’); const
http2 = require(‘spdy’); const path = require(‘path’); const options = {
key: fs.readFileSync(‘./keys/privatekey.pem’), cert:
fs.readFileSync(‘./keys/certificate.pem’) }; const app = new express();
http2 .createServer(options, app) .listen(8080, ()=>{
console.log(`Server is listening on . You can
open the URL in the browser.`) } ) app.use(“/”,(req,res)=>{
res.send(“hello http2!”); })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require(‘express’);
const fs =  require(‘fs’);
const http2 = require(‘spdy’);
const path = require(‘path’);
const options = {
    key: fs.readFileSync(‘./keys/privatekey.pem’),
    cert: fs.readFileSync(‘./keys/certificate.pem’)
};
const app = new express();
http2
  .createServer(options, app)
  .listen(8080, ()=>{
    console.log(`Server is listening on https://localhost:8080.
     You can open the URL in the browser.`)
  }
)
app.use("/",(req,res)=>{
    
  res.send("hello http2!");
})

如上,对于已存在的项目只要修改几行代码就可以使用http2.0了。

请求头和响应头:

说明:新版的Chrome,对不安全的证书(如本地的自签名服务)会降级到http1.1,firefox不会出现此问题。

启动server push

JavaScript

app.get(“/”,(req,res)=>{ var stream = res.push(‘/app.js’, {
//服务器推送 status: 200, // optional method: ‘GET’, // optional
request: { accept: ‘*/*’ }, response: { ‘content-type’:
‘application/javascript’ } }) stream.on(‘error’, function() { })
stream.end(‘console.log(“http2 push stream, by Lucien “);’)
res.send(`hello http2! <script
src=”/app.js”></script>`);//express 并没有host static
,这个app.js 来自push })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.get("/",(req,res)=>{
    var stream = res.push(‘/app.js’, {   //服务器推送
    status: 200, // optional
    method: ‘GET’, // optional
    request: {
      accept: ‘*/*’
    },
    response: {
      ‘content-type’: ‘application/javascript’
    }
  })
  stream.on(‘error’, function() {
  })
  stream.end(‘console.log("http2 push stream, by Lucien ");’)
 
  res.send(`hello http2!
    <script src="/app.js"></script>`);//express 并没有host static ,这个app.js 来自push
})

源码在github

响应

1. 二进制协议

HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式

图片 33

 

由上图可以看到HTTP2在原来的应用层和HTTP层添加了一层二进制传输。

二进制协议的一个好处是,可以定义额外的帧。

HTTP/2
定义了近十种帧(详情可分析抓包文件),为将来的高级应用打好了基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多。
RFC7540:Frame Definitions

图片 34

协议中定义的帧

相关文章

发表评论

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