前端规范

    1. 规范说明

    此为前端开发团队遵循和约定的代码书写规范,意在提高代码的规范性和可维护性。
    此规范为参考规范,不全是硬性要求,部分硬性约定见下一条书写规范,统一团队编码规范和风格。让所有代码都是有规可循的,并且能够得到沉淀,减少重复劳动。

    1.1 结构说明

    项目结构
    —-|—- CSS文件结构
    —-|—- JS文件结构

    2. 书写规范

    2.1 样式与内容分离

    2.1.1 项目结构

    --- 
     |---- index.html             入口页    
     |---- js/                    JS //具体见JS细化结构                 
     |---- css/                   CSS //具体见CSS细化结构

    2.1.2 重构步骤约定

    1. index.html 全部样式附着于 class="xxx" 注: 此时文件中不包含任何一个 id=”xxx”
    2. 为上一步书写 CSS style
      [至此重构完成]
    3. 开始书写js交互文件,使用 IDClass 定位被操作句柄
    4. index.html 中需要的地方添加 id="xxx"data-xxx="xxx"
      [至此交互效果完成]

    2.1.3 命名规范

    • 文件及文件夹: 全部英文小写字母+数字或连接符”- , _ “,不可出现其他字符 如:../css/style.css, jquery.1.x.x.js
    • 文件:调用 /libs 文件需包含版本号,压缩文件需包含min关键词,其他插件则可不包含 如:/libs/jquery.1.9.1.js /libs/modernizr-1.7.min.js fileuploader.js plugins.js
    • ID: 匈牙利命名法 & 小駝峰式命名法
      如:firstName topBoxList footerCopyright
    • Class: [减号连接符]
      如:top-item main-box box-list-item-1
    • 尽量使用语义明确的单词命名,避免 left bottom 等方位性的词语

    2.1.4 格式&编码

    • 文本文件: .xxx UTF-8(无BOM) 编码
    • 图片文件: .png (PNG-24) .jpg (压缩率8-12)
    • 动态图片: .gif
    • 压缩文件: .tar.gz .zip

    2.2 CSS 细化规范

    2.2.1 CSS 文件结构

    --- ../css/
     |---- css/libs/reset.css                  CSS reset文件
     |----   
     |---- css/images/                          CSS-sprite 图片     
     |---- css/style.css                        CSS 样式表
     |----   
     |---- css/images/xxx/sprite.png           xxx  CSS-sprite 图片
     |---- css/xxx-style.css                   xxx  样式表

    2.2.2 CSS(含Less) 文件结构

    --- ../css/
     |---- css/libs/reset.less            CSS reset文件
     |---- css/libs/elements.less         Less 函数复用文件
     |----   
     |---- css/images/                          CSS-sprite 图片     
     |---- css/style.less                      主样式Less
     |---- css/style.css                       less -> css 自动生成
     |----   
     |---- css/images/xxx/sprite.png           xxx  CSS-sprite 图片
     |---- css/xxx-style.less                  xxx  Less
     |---- css/xxx-style.css                   less -> css 自动生成

    2.2.3 使用Less

    .less文件头部引入 reset.less & elements.less,之后调用如下属性传参即可,具体使用说明见 -> Lesselements 官方文档

    @import "libs/reset.less";  
    @import "libs/elements.less";
    
    .gradient
    .bw-gradient
    .bordered
    .drop-shadow
    .rounded
    .border-radius
    .opacity
    .transition-duration
    .rotation
    .scale
    .transition
    .inner-shadow
    .box-shadow
    .columns
    .translate

    2.2.4 CSS reset

    CSS reset 文件使用:reset.cssreset.less

    • reset文件用于重设各个浏览器的默认样式方案,解决其引起的耦合问题。
    • 也可使用 .clearfix 清除浮动

    2.2.5 CSS 注释格式约定

    /*
    @name: Drop Down Menu
    @description: Style of top bar drop down menu.
    @require: reset.css
    @author: Andy Huang(andyahung@geekpark.net)
    */

    其中,@require为可选项

    • CSS换行制表:使用 2 或4 个空格,而非[Tab]
    • 书写格式:
      • CSS名称+冒号+属性
        如:.box1 {float:left;}
      • 建议保留{左侧空格,字体名用\包含
        如:.box1,.box2,.box3 {font-family:Courier,'Courier New';}
      • 避免中文,或使用转义,推荐前者
        如:font-family: "Microsoft YaHei"; font-family:\5fae\8f6f\96c5\9ed1;

    2.2.6 CSS各属性的排列顺序:不做硬性要求

    如:以下2种顺序均可

    .box {
      /* 顺序1 */
      background: none repeat scroll 0 0 transparent;
      bottom: 11px;
      position: relative;
      width: 22px;
      z-index: 33;
    }
    .box {
      /* 顺序2 */
      z-index: 33;
      width: 22px;
      bottom: 11px;
      background: transparent none repeat scroll 0 0 ;
      position: relative;
    }

    2.2.7 CSS嵌套规则

    /* 推荐嵌套层级 */
    .ui-icon-rarr{}
    .ui-icon-larr{}
    .ui-title{}
    .ui-nav .ui-list{}
    .ui-table .ui-list{}
    
    /* 不推荐 */
    .ui-icon-rarr{}
    .ui-icon-larr{}
    .ui-title{}
    .ui-list{}
    .ui-nav{}

    2.2.8 CSS格式化

    勿格式化,保留下面这种格式,增加可读性和可维护性,后期后台程序(如:PHP/Python)会将CSS压缩成 一行 输出:

    .box{
      /* 勿格式化,增加可读性 */
      background: none repeat scroll 0 0 transparent;
      bottom: 11px;
      position: relative;
      width: 22px;
      z-index: 33;
    }

    2.3 XHTML 细化规范

    2.3.1 HTML 注释格式约定

    <!--
    @name: Drop Down Menu
    @description: Style of top bar drop down menu.
    @author: Andy Huang(andyahung@geekpark.net)
    -->
    
    <div id="header">
        <div class="xxx">
            <span>HTML行内注释格式</span>
        </div>
    </div><!-- #header end-->
    • HTML换行缩进:采用 2 空格

    2.3.2 HTML嵌套规则

    /* 推荐嵌套层级 */
      <ul class="ui-nav">
        <li class="ui-nav-item"> some text
          <ul class="ui-nav-item-child">
            <li> some text
              <ul class="ui-list">
                <li class="ui-list-item"> some text</li>
              </ul
            ...
           </ul>
        </li>
        <li
        ...
      </ul>

    2.3.4 * 第一行统一使用HTML5标准:

    <!DOCTYPE html>
    <html dir="ltr" lang="zh-CN">
    <head>
      <meta charset="utf-8" />
      <title>极客公园 | 创新者社区</title>
      <meta name="keywords" content="xxxx, xxx, xxxxx" />
      <meta name="description" content="xxxxxxxxxxxxxxxxxxxx" />

    2.3.5 Meta 的使用:(需要根据具体需求按需选择)可参看:cool-head

    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta http-equiv="Cache-Control" content="max-age=7200" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="http://feeds.geekpark.net/" />
    <link rel="shortcut icon" href="favicon.ico">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
    
    <script type="text/javascript" src="/js/xxx.js"></script>
    <link rel="stylesheet" href="/css/xxx.css">
    
    <script type="text/javascript">
        //Google 统计代码 的位置在离</head>最近的位置
    </script>
    • <img>标签默认缺省格式:<img src="xxx.png" alt="缺省时文字" /> 避免出现src=”” 问题
    • <a>标签缺省格式:<a href="###" title="链接名称">xxx</> 注:target="_blank" 根据需求决定
    • <a>标签预留链接占位符使用###,参见:a标签占位符问题
    • 所有标签需要符合XHTML标准闭合
    • 一律统一标签结尾斜杠的书写形式:<br /> <hr /> 注意之间空格
    • 避免使用已过时标签,如:<b> <u> <i> 而用 <strong> <em>等代替
    • 使用data-xxx来添加自定义数据,如:<input data-xxx="yyy"/>
    • 避免使用style="xxx:xxx;"的内联样式表
    • 特殊符号使用参考HTML 符号实体

    2.4 JS 细化规范

    2.4.1 JS 文件结构

    ---/js/
    |---- /libs/plugin-1/       使用到的js插件1  
    |---- /libs/plugin-2/       使用到的js插件2  
    |---- /libs/plugin-3/       使用到的js插件3  
    |---- script.js             单独书写的js  
    |---- plugins.js            调用的plugins汇总  
    |---- juqery-1.9.x.min.js   调用jq库文件  
    • JS 换行缩进:采用 4 空格
    • 结束行需添加分号;
    • jQuery变量要求首字符为 $, 私有变量:首字符为_; 尽量避免全局变量.
    • 避免使用 eval(),setTimeOut使用调用函数,考虑重绘,回流 操作对页面影响 参看:reflows,repaints
    • JS调试使用console.log()console.dir()进行,避免使用弹出框,线上版需要注释所有调试代码
    • JS压缩混淆工具: JS Compressor 如果使用了压缩,需要留 name-src.js在同路径供今后修改使用
    2.4.2 jQuery Call
    <!-- Grab Google CDN jQuery. fall back to local if necessary -->  
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>  
    <script>!window.jQuery && document.write('<script src="js/jquery-1.7.2.min.js"><\/script>')</script>            
    2.4.3 jQuery Plugin
    2.4.4 JSON格式规范

    参考JSON Style Guide翻译,原版:Google JSON Style Guide

    HTML 细化规范

    • HTML head部分的结构参看:cool-head (摘取必要内容即可)
    • css 文件置于都 头部
    • jQueryGoogle Analytics引用置于 头部
    • 其他效果js统计代码 文件置于 尾部
    • HTML 代码尽量过一遍HTML5 验证
    • HTML 占位图片使用 temp.im & placehold.us 图片服务

    2.5 Responsive Web 规范

    从设计之处就坚持”Mobile First“的理念,在重构页面的时候要灵活采取响应式的解决方案。

    2.5.1 响应式实现途径

    * 设置 meta viewport 属性

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

    * 引入不同尺寸设备的样式表

    <link rel="stylesheet" type="text/css" href="style.css" media="screen, handheld" />
    <link rel="stylesheet" type="text/css" href="enhanced.css" media="screen  and (min-width: 40.5em)" />
    <!--[if (lt IE 9)&(!IEMobile)]>
    <link rel="stylesheet" type="text/css" href="enhanced.css" />
    <![endif]-->

    * 使用 CSS Media Queries 方法

    @media screen and (max-width: 40.5em) {
      .product-img {
        width: auto;
        float: none;
      }
    }
    @media screen and (max-width: 480px) {
    }

    * JS控制导航栏在 resize 事件 触发后的可见性,如:

    $(w).resize(function(){ //Update dimensions on resize
      sw = document.documentElement.clientWidth;
      sh = document.documentElement.clientHeight;
      checkMobile();
    });
    //Check if Mobile
    function checkMobile() {
      mobile = (sw > breakpoint) ? false : true;
      if (!mobile) { //If Not Mobile
        $('[role="tabpanel"],#nav,#search').show(); //Show full navigation and search
      } else { //Hide 
        if(!$('#nav-anchors a').hasClass('active')) {
          $('#nav,#search').hide(); //Hide full navigation and search
        }
      }
    }

    2.5.2 响应式解决方案

    * 弹性图片

    img {
      max-width: 100%;
      height: auto;
      width: auto\9; /* ie8 */
    }

    * 自适应嵌入媒体

    .video embed, .video object, .video iframe {
      width: 100%;
      height: auto;
    }

    * 禁用iPhone字体自适应功能:

    html {
      -webkit-text-size-adjust: none;
    }

    * 让 IE 9 以下的IE版本支持响应式:

    <!--[if lt IE 9]>
      <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
    <![endif]-->

    2.6 Newletter 制作规范

    • CSS只可使用 内联样式表 ,如:style="margin:0;"
    • 设计之初遵循: 图上无文本,文本后无底纹 的规则
    • 使用 MailChimp HTML Email CSS Fix 参看下文链接
    • 引入 CSS Reset for HTML Email 参看下文链接
    • 使用Table布局而非DIV
    • 所有图片使用IMG标签,如:<img style="style="display:block" "src="" />
    • 可以适当使用占位符spacer.gif
    • 多用<br />换行而非<p>
    • 整体最佳宽度为:550-600px
    • 不使用Javascript
    • 正式发送给用户之前,多次测试

    更多细节参考下面链接:
    12 Killer Tips and Tricks for Building HTML Email
    Coding HTML Newsletters (EDM)

移动端网络优化

    移动端网络优化

    介绍下针对移动端的网络优化,不限于 Android,同样适用于 iOS 和 H5。
    这篇文章首发在微信公众号 codekk

     

    本文为性能优化系列第四篇,目前性能调优专题已完成以下部分:
    性能优化总纲——性能问题及性能调优方式
    性能优化第四篇——移动网络优化
    性能优化第三篇——代码优化
    性能优化第二篇——布局优化
    性能优化第一篇——数据库性能优化
    Android 性能调优工具 TraceView
    性能优化实例

     

    一个网络请求可以简单分为连接服务器 -> 获取数据两个部分。
    其中连接服务器前还包括 DNS 解析的过程;获取数据后可能会对数据进行缓存。

     

    一、连接服务器优化策略

    1. 不用域名,用 IP 直连
    省去 DNS 解析过程,DNS 全名 Domain Name System,解析意指根据域名得到其对应的 IP 地址。 如 http://www.codekk.com 的域名解析结果就是 104.236.147.76。

     

    首次域名解析一般需要几百毫秒,可通过直接向 IP 而非域名请求,节省掉这部分时间,同时可以预防域名劫持等带来的风险。

     

    当然为了安全和扩展考虑,这个 IP 可能是一个动态更新的 IP 列表,并在 IP 不可用情况下通过域名访问。

     

    2. 服务器合理部署
    服务器多运营商多地部署,一般至少含三大运营商、南中北三地部署。

     

    配合上面说到的动态 IP 列表,支持优先级,每次根据地域、网络类型等选择最优的服务器 IP 进行连接。

     

    对于服务器端还可以调优服务器的 TCP 拥塞窗口大小、重传超时时间(RTO)、最大传输单元(MTU)等。

     

    二、获取数据优化策略

    1. 连接复用
    节省连接建立时间,如开启 keep-alive。

     

    Http 1.1 默认启动了 keep-alive。对于 Android 来说默认情况下 HttpURLConnection 和 HttpClient 都开启了 keep-alive。只是 2.2 之前 HttpURLConnection 存在影响连接池的 Bug,具体可见:Android HttpURLConnection 及 HttpClient 选择

     

    2. 请求合并
    即将多个请求合并为一个进行请求,比较常见的就是网页中的 CSS Image Sprites。 如果某个页面内请求过多,也可以考虑做一定的请求合并。

     

    3. 减小请求数据大小
    (1) 对于 POST 请求,Body 可以做 Gzip 压缩,如日志。

     

    (2) 对请求头进行压缩
    这个 Http 1.1 不支持,SPDY 及 Http 2.0 支持。 Http 1.1 可以通过服务端对前一个请求的请求头进行缓存,后面相同请求头用 md5 之类的 id 来表示即可。

     

    4. CDN 缓存静态资源
    缓存常见的图片、JS、CSS 等静态资源。

     

    5. 减小返回数据大小
    (1) 压缩
    一般 API 数据使用 Gzip 压缩,下图是之前测试的 Gzip 压缩前后对比图。 android-http-compare

     

    (2) 精简数据格式
    如 JSON 代替 XML,WebP 代替其他图片格式。关注公众号 codekk,回复 20 查看关于 WebP 的介绍。

     

    (3) 对于不同的设备不同网络返回不同的内容 如不同分辨率图片大小。

     

    (4) 增量更新
    需要数据更新时,可考虑增量更新。如常见的服务端进行 bsdiff,客户端进行 bspatch。

     

    (5) 大文件下载
    支持断点续传,并缓存 Http Resonse 的 ETag 标识,下次请求时带上,从而确定是否数据改变过,未改变则直接返回 304。

     

    6. 数据缓存
    缓存获取到的数据,在一定的有效时间内再次请求可以直接从缓存读取数据。

     

    关于 Http 缓存规则 Grumoon 在 Volley 源码解析最后杂谈中有详细介绍。

     

    三、其他优化手段

    这类优化方式在性能优化系列总篇中已经有过完整介绍
    1. 预取
    包括预连接、预取数据。

     

    2. 分优先级、延迟部分请求
    将不重要的请求延迟,这样既可以削峰减少并发、又可以和后面类似的请求做合并。

     

    3. 多连接
    对于较大文件,如大图片、文件下载可考虑多连接。 需要控制请求的最大并发量,毕竟移动端网络受限。

     

    四、监控

    优化需要通过数据对比才能看出效果,所以监控系统必不可少,通过前后端的数据监控确定调优效果。

     

    注:服务器部署方面的优化有参考手 Q 和 QZone 去年的技术分享。

前端工程与性能优化

    每个参与过开发企业级web应用的前端工程师或许都曾思考过前端性能优化方面的问题。我们有雅虎14条性能优化原则,还有两本很经典的性能优化指导书:《高性能网站建设指南》、《高性能网站建设进阶指南》。经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来。这些性能优化原则大概是在7年前提出的,对于web性能优化至今都有非常重要的指导意义。

    高性能网站建设指南

    然而,对于构建大型web应用的团队来说,要坚持贯彻这些优化原则并不是一件十分容易的事。因为优化原则中很多要求是与工程管理相违背的,比如 把css放在头部把js放在尾部 这两条原则,我们不能让团队的工程师在写样式和脚本引用的时候都去修改一个相同的页面文件。这样做会严重影响团队成员间并行开发的效率,尤其是在团队有版本管理的情况下,每天要花大量的时间进行代码修改合并,这项成本是难以接受的。因此在前端工程界,总会看到周期性的性能优化工作,辛勤的前端工程师们每到月圆之夜就会倾巢出动根据优化原则做一次性能优化。

    性能优化是一个工程问题

    本文将从一个全新的视角来思考web性能优化与前端工程之间的关系,揭示前端性能优化在前端架构及开发工具设计层面的实现思路。

    性能优化原则及分类

    po主先假设本文的读者是有前端开发经验的工程师,并对企业级web应用开发及性能优化有一定的思考,因此我不会重复介绍雅虎14条性能优化原则。如果您没有这些前续知识,请移步 这里 来学习。

    首先,我们把雅虎14条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点做一次梳理,按照优化方向分类,可以得到这样一张表格:

    优化方向 优化手段
    请求数量 合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域
    请求带宽 开启GZip,精简JavaScript,移除重复脚本,图像优化
    缓存利用 使用CDN,使用外部JavaScript和CSS,添加Expires头,
    减少DNS查找,配置ETag,使AjaX可缓存
    页面结构 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出
    代码校验 避免CSS表达式,避免重定向

    目前大多数前端团队可以利用 yui compressor 或者 google closure compiler 等压缩工具很容易做到 精简Javascript 这条原则;同样的,也可以使用图片压缩工具对图像进行压缩,实现 图像优化 原则。这两条原则是对单个资源的处理,因此不会引起任何工程方面的问题。很多团队也通过引入代码校验流程来确保实现 避免css表达式避免重定向 原则。目前绝大多数互联网公司也已经开启了服务端的Gzip压缩,并使用CDN实现静态资源的缓存和快速访问;一些技术实力雄厚的前端团队甚至研发出了自动CSS Sprites工具,解决了CSS Sprites在工程维护方面的难题。使用“查找-替换”思路,我们似乎也可以很好的实现 划分主域 原则。

    我们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些还没有很好实现的优化原则。再来回顾一下之前的性能优化分类:

    优化方向 优化手段
    请求数量 合并脚本和样式表,拆分初始化负载
    请求带宽 移除重复脚本
    缓存利用 添加Expires头,配置ETag,使Ajax可缓存
    页面结构 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

    有很多顶尖的前端团队可以将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很好的解决这些问题。因此,本文将就这些原则的解决方案做进一步的分析与讲解,从而为那些还没有进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化工程化方向上交流一下彼此的心得。

    静态资源版本更新与缓存

    缓存利用 分类中保留了 添加Expires头配置ETag 两项。或许有些人会质疑,明明这两项只要配置了服务器的相关选项就可以实现,为什么说它们难以解决呢?确实,开启这两项很容易,但开启了缓存后,我们的项目就开始面临另一个挑战: 如何更新这些缓存?

    相信大多数团队也找到了类似的答案,它和《高性能网站建设指南》关于“添加Expires头”所说的原则一样——修订文件名。即:

    最有效的解决方案是修改其所有链接,这样,全新的请求将从原始服务器下载最新的内容。

    思路没错,但要怎么改变链接呢?变成什么样的链接才能有效更新缓存,又能最大限度避免那些没有修改过的文件缓存不失效呢?

    先来看看现在一般前端团队的做法:

    <h1>hello world</h1>
    
    <script type="text/javascript" src="a.js?t=201404231123"></script>
    <script type="text/javascript" src="b.js?t=201404231123"></script>
    <script type="text/javascript" src="c.js?t=201404231123"></script>
    <script type="text/javascript" src="d.js?t=201404231123"></script>
    <script type="text/javascript" src="e.js?t=201404231123"></script>

    ps: 也有团队采用构建版本号为静态资源请求添加query,它们在本质上是没有区别的,在此就不赘述了。

    接下来,项目升级,比如页面上的html结构发生变化,对应还要修改 a.js 这个文件,得到的构建结果如下:

    <header>hello world</header>
    
    <script type="text/javascript" src="a.js?t=201404231826"></script>
    <script type="text/javascript" src="b.js?t=201404231826"></script>
    <script type="text/javascript" src="c.js?t=201404231826"></script>
    <script type="text/javascript" src="d.js?t=201404231826"></script>
    <script type="text/javascript" src="e.js?t=201404231826"></script>

    为了触发用户浏览器的缓存更新,我们需要更改静态资源的url地址,如果采用构建信息(时间戳、版本号等)作为url修改的依据,如上述代码所示,我们只修改了一个a.js文件,但再次构建会让所有请求都更改了url地址,用户再度访问页面那些没有修改过的静态资源的(b.js,b.js,c.js,d.js,e.js)的浏览器缓存也一同失效了。

    使用构建信息作为静态资源更新标记会导致每次构建发布后所有静态资源都被迫更新,浏览器缓存利用率降低,给性能带来伤害。

    此外,采用添加query的方式来清除缓存还有一个弊端,就是 覆盖式发布 的上线问题。

    覆盖式发布

    采用query更新缓存的方式实际上要覆盖线上文件的,index.html和a.js总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。尤其是当页面是后端渲染的模板的时候,静态资源和模板是部署在不同的机器集群上的,上线的过程中,静态资源和页面文件的部署时间间隔可能会非常长,对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问。在这个时间间隔中,访问了网站的用户会发生什么情况呢?

    1. 如果先覆盖index.html,后覆盖a.js,用户在这个时间间隙访问,会得到新的index.html配合旧的a.js的情况,从而出现错误的页面。
    2. 如果先覆盖a.js,后覆盖index.html,用户在这个间隙访问,会得到旧的index.html配合新的a.js的情况,从而也出现了错误的页面。

    这就是为什么大型web应用在版本上线的过程中经常会较集中的出现前端报错日志的原因,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的原因之一。

    对于静态资源缓存更新的问题,目前来说最优方案就是 基于文件内容的hash版本冗余机制 了。也就是说,我们希望项目源码是这么写的:

    <script type="text/javascript" src="a.js"></script>

    发布后代码变成

    <script type="text/javascript" src="a_8244e91.js"></script>

    也就是a.js发布出来后被修改了文件名,产生一个新文件,并不是覆盖已有文件。其中”_82244e91”这串字符是根据a.js的文件内容进行hash运算得到的,只有文件内容发生变化了才会有更改。由于将文件发布为带有hash的新文件,而不是同名文件覆盖,因此不会出现上述说的那些问题。同时,这么做还有其他的好处:

    1. 上线的a.js不是同名文件覆盖,而是文件名+hash的冗余,所以可以先上线静态资源,再上线html页面,不存在间隙问题;
    2. 遇到问题回滚版本的时候,无需回滚a.js,只须回滚页面即可;
    3. 由于静态资源版本号是文件内容的hash,因此所有静态资源可以开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增;

    以文件内容的hash值为依据生产新文件的非覆盖式发布策略是解决静态资源缓存更新最有效的手段。

    虽然这种方案是相比之下最完美的解决方案,但它无法通过手工的形式来维护,因为要依靠手工的形式来计算和替换hash值,并生成相应的文件,将是一项非常繁琐且容易出错的工作,因此我们需要借助工具来处理。

    用grunt来实现md5功能是非常困难的,因为grunt只是一个task管理器,而md5计算需要构建工具具有递归编译的能,而不是简单的任务调度。考虑这样的例子:

    md5计算过程

    由于我们的资源版本号是通过对文件内容进行hash运算得到,如上图所示,index.html中引用的a.css文件的内容其实也包含了a.png的hash运算结果,因此我们在修改index.html中a.css的引用时,不能直接计算a.css的内容hash,而是要先计算出a.png的内容hash,替换a.css中的引用,得到了a.css的最终内容,再做hash运算,最后替换index.html中的引用。

    计算index.html中引用的a.css文件的url过程:
    1. 压缩a.png后计算其内容的md5值
    2. 将a.png的md5写入a.css,再压缩a.css,计算其内容的md5值
    3. 将a.css的md5值写入到index.html中

    grunt等task-based的工具是很难在task之间协作处理这样的需求的。

    在解决了基于内容hash的版本更新问题之后,我们可以将所有前端静态资源开启永久强缓存,每次版本发布都可以首先让静态资源全量上线,再进一步上线模板或者页面文件,再也不用担心各种缓存和时间间隙的问题了!

    静态资源管理与模块化框架

    解决了静态资源缓存问题之后,让我们再来看看前面的优化原则表还剩些什么:

    优化方向 优化手段
    请求数量 合并脚本和样式表,拆分初始化负载
    请求带宽 移除重复脚本
    缓存利用 使Ajax可缓存
    页面结构 将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

    很不幸,剩下的优化原则都不是使用工具就能很好实现的。或许有人会辩驳:“我用某某工具可以实现脚本和样式表合并”。嗯,必须承认,使用工具进行资源合并并替换引用或许是一个不错的办法,但在大型web应用,这种方式有一些非常严重的缺陷,来看一个很熟悉的例子 :

    第一天

    某个web产品页面有A、B、C三个资源

    第二天

    工程师根据“减少HTTP请求”的优化原则合并了资源

    第三天

    产品经理要求C模块按需出现,此时C资源已出现多余的可能

    第四天

    C模块不再需要了,注释掉吧!代码1秒钟搞定,但C资源通常不敢轻易剔除

    后来

    不知不觉中,性能优化变成了性能恶化……

    这个例子来自 Facebook静态网页资源的管理和优化@Velocity China 2010

    事实上,使用工具在线下进行静态资源合并是无法解决资源按需加载的问题的。如果解决不了按需加载,则必会导致资源的冗余;此外,线下通过工具实现的资源合并通常会使得资源加载和使用的分离,比如在页面头部或配置文件中写资源引用及合并信息,而用到这些资源的html组件写在了页面其他地方,这种书写方式在工程上非常容易引起维护不同步的问题,导致使用资源的代码删除了,引用资源的代码却还在的情况。因此,在工业上要实现资源合并至少要满足如下需求:

    1. 确实能减少HTTP请求,这是基本要求(合并)
    2. 在使用资源的地方引用资源(就近依赖),不使用不加载(按需)
    3. 虽然资源引用不是集中书写的,但资源引用的代码最终还能出现在页面头部(css)或尾部(js)
    4. 能够避免重复加载资源(去重)

    将以上要求综合考虑,不难发现,单纯依靠前端技术或者工具处理是很难达到这些理想要求的。

    接下来我会讲述一种新的模板架构设计,用以实现前面说到那些性能优化原则,同时满足工程开发和维护的需要,这种架构设计的核心思想就是:

    基于依赖关系表的静态资源管理系统与模块化框架设计

    考虑一段这样的页面代码:

    <html>
    <head>
        <title>page</title>
        <link rel="stylesheet" type="text/css" href="a.css"/>
        <link rel="stylesheet" type="text/css" href="b.css"/>
        <link rel="stylesheet" type="text/css" href="c.css"/>
    </head>
    <body>
        <div> content of module a </div>
        <div> content of module b </div>
        <div> content of module c </div>
    </body>
    </html>

    根据资源合并需求中的第二项,我们希望资源引用与使用能尽量靠近,这样将来维护起来会更容易一些,因此,理想的源码是:

    <html>
    <head>
        <title>page</title>
    </head>
    <body>
        <link rel="stylesheet" type="text/css" href="a.css"/>
        <div> content of module a </div>
    
        <link rel="stylesheet" type="text/css" href="b.css"/>
        <div> content of module b </div>
    
        <link rel="stylesheet" type="text/css" href="c.css"/>
        <div> content of module c </div>
    </body>
    </html>

    当然,把这样的页面直接送达给浏览器用户是会有严重的页面闪烁问题的,所以我们实际上仍然希望最终页面输出的结果还是如最开始的截图一样,将css放在头部输出。这就意味着,页面结构需要有一些调整,并且有能力收集资源加载需求,那么我们考虑一下这样的源码(以php为例):

    <html>
    <head>
        <title>page</title>
        <!--[ CSS LINKS PLACEHOLDER ]-->
    </head>
    <body>
        <?php require_static('a.css'); ?>
        <div> content of module a </div>
    
        <?php require_static('b.css'); ?>
        <div> content of module b </div>
    
        <?php require_static('c.css'); ?>
        <div> content of module c </div>
    </body>
    </html>

    在页面的头部插入一个html注释 <!--[CSS LINKS PLACEHOLDER]--> 作为占位,而将原来字面书写的资源引用改成模板接口 require_static 调用,该接口负责收集页面所需资源。

    require_static接口实现非常简单,就是准备一个数组,收集资源引用,并且可以去重。最后在页面输出的前一刻,我们将require_static在运行时收集到的 a.cssb.cssc.css 三个资源拼接成html标签,替换掉注释占位 <!--[CSS LINKS PLACEHOLDER]-->,从而得到我们需要的页面结构。

    经过实践总结,可以发现模板层面只要实现三个开发接口,就可以比较完美的实现目前遗留的大部分性能优化原则,这三个接口分别是:

    1. require_static(res_id):收集资源加载需求的接口,参数是静态资源id。
    2. load_widget(wiget_id):加载拆分成小组件模板的接口。你可以叫它为widget、component或者pagelet之类的。总之,我们需要一个接口把一个大的页面模板拆分成一个个的小部分来维护,最后在原来的页面中以组件为单位来加载这些小部件。
    3. script(code):收集写在模板中的js脚本,使之出现的页面底部,从而实现性能优化原则中的 将js放在页面底部 原则。

    实现了这些接口之后,一个重构后的模板页面的源代码可能看起来就是这样的了:

    <html>
    <head>
        <title>page</title>
        <?php require_static('jquery.js'); ?>
        <?php require_static('bootstrap.css'); ?>
        <?php require_static('bootstrap.js'); ?>
        <!--[ CSS LINKS PLACEHOLDER ]-->
    </head>
    <body>
        <?php load_widget('a'); ?>
        <?php load_widget('b'); ?>
        <?php load_widget('c'); ?>
        <!--[ SCRIPTS PLACEHOLDER ]-->
    </body>
    </html>

    而最终在模板解析的过程中,资源收集与去重、页面script收集、占位符替换操作,最终从服务端发送出来的html代码为:

    <html>
    <head>
        <title>page</title>
        <link rel="stylesheet" type="text/css" href="bootstrap.css"/>
        <link rel="stylesheet" type="text/css" href="a.css"/>
        <link rel="stylesheet" type="text/css" href="b.css"/>
        <link rel="stylesheet" type="text/css" href="c.css"/>
    </head>
    <body>
        <div> content of module a </div>
        <div> content of module b </div>
        <div> content of module c </div>
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="bootstrap.js"></script>
        <script type="text/javascript" src="a.js"></script>
        <script type="text/javascript" src="b.js"></script>
        <script type="text/javascript" src="c.js"></script>
    </body>
    </html>

    不难看出,我们目前已经实现了 按需加载将脚本放在底部将样式表放在头部 三项优化原则。

    前面讲到静态资源在上线后需要添加hash戳作为版本标识,那么这种使用模板语言来收集的静态资源该如何实现这项功能呢?

    答案是:静态资源依赖关系表。

    考虑这样的目录结构:

    project
        ├── widget
        │   ├── a
        │   │   ├── a.css
        │   │   ├── a.js
        │   │   └── a.php
        │   ├── b
        │   │   ├── b.css
        │   │   ├── b.js
        │   │   └── b.php
        │   └── c
        │       ├── c.css
        │       ├── c.js
        │       └── c.php
        ├── bootstrap.css
        ├── bootstrap.js
        ├── index.php
        └── jquery.js
    

    如果我们可以使用工具扫描整个project目录,然后创建一张资源表,同时记录每个资源的部署路径,得到这样的一张表:

    {
        "res" : {
            "widget/a/a.css" : "/widget/a/a_1688c82.css",
            "widget/a/a.js"  : "/widget/a/a_ac3123s.js",
            "widget/b/b.css" : "/widget/b/b_52923ed.css",
            "widget/b/b.js"  : "/widget/b/b_a5cd123.js",
            "widget/c/c.css" : "/widget/c/c_03cab13.css",
            "widget/c/c.js"  : "/widget/c/c_bf0ae3f.js",
            "jquery.js"      : "/jquery_9151577.js",
            "bootstrap.css"  : "/bootstrap_f5ba12d.css",
            "bootstrap.js"   : "/bootstrap_a0b3ef9.js"
        },
        "pkg" : {}
    }

    基于这张表,我们就很容易实现 require_static(file_id)load_widget(widget_id) 这两个模板接口了。以load_widget为例:

    function load_widget($id){
        //从json文件中读取资源表
        $map = load_map();
        //查找静态资源
        $filename = 'widget/' . $id . '/' . $id;
        //查找js文件
        $js = $filename . '.js';
        if(isset($map['res'][$js])) {
            //如果有对应的js资源,就收集起来
            collect_js_static($map['res'][$js]);
        }
        //查找css文件
        $css = $filename . '.css';
        if(isset($map['res'][$css])) {
            //如果有对应的css资源,就收集起来
            collect_css_static($map['res'][$css]);
        }
        include $filename . '.php';
    }

    利用查表来解决md5戳的问题,这样,我们的页面最终送达给用户的结果就是这样的:

    <html>
    <head>
        <title>page</title>
        <link rel="stylesheet" type="text/css" href="/bootstrap_f5ba12d.css"/>
        <link rel="stylesheet" type="text/css" href="/widget/a/a_1688c82.css"/>
        <link rel="stylesheet" type="text/css" href="/widget/b/b_52923ed.css"/>
        <link rel="stylesheet" type="text/css" href="/widget/c/c_03cab13.css"/>
    </head>
    <body>
        <div> content of module a </div>
        <div> content of module b </div>
        <div> content of module c </div>
        <script type="text/javascript" src="/jquery_9151577.js"></script>
        <script type="text/javascript" src="/bootstrap_a0b3ef9.js"></script>
        <script type="text/javascript" src="/widget/a/a_ac3123s.js"></script>
        <script type="text/javascript" src="/widget/b/b_a5cd123.js"></script>
        <script type="text/javascript" src="/widget/c/c_bf0ae3f.js"></script>
    </body>
    </html>

    接下来,我们讨论基于表的设计思想上是如何实现静态资源合并的。或许有些团队使用过combo服务,也就是我们在最终拼接生成页面资源引用的时候,并不是生成多个独立的link标签,而是将资源地址拼接成一个url路径,请求一种线上的动态资源合并服务,从而实现减少HTTP请求的需求,比如前面的例子,稍作调整即可得到这样的结果:

    <html>
    <head>
        <title>page</title>
        <link rel="stylesheet" type="text/css" href="/??bootstrap_f5ba12d.css,widget/a/a_1688c82.css,widget/b/b_52923ed.css,widget/c/c_03cab13.css"/>
    </head>
    <body>
        <div> content of module a </div>
        <div> content of module b </div>
        <div> content of module c </div>
        <script type="text/javascript" src="/??jquery_9151577.js,bootstrap_a0b3ef9.js,widget/a/a_ac3123s.js,widget/b/b_a5cd123.js,widget/c/c_bf0ae3f.js"></script>
    </body>
    </html>

    这个 /??file1,file2,file3,… 的url请求响应就是动态combo服务提供的,它的原理很简单,就是根据url找到对应的多个文件,合并成一个文件来响应请求,并将其缓存,以加快访问速度。

    这种方法很巧妙,有些服务器甚至直接集成了这类模块来方便的开启此项服务,这种做法也是大多数大型web应用的资源合并做法。但它也存在一些缺陷:

    1. 浏览器有url长度限制,因此不能无限制的合并资源。
    2. 如果用户在网站内有公共资源的两个页面间跳转访问,由于两个页面的combo的url不一样导致用户不能利用浏览器缓存来加快对公共资源的访问速度。
    3. 如果combo的url中任何一个文件发生改变,都会导致整个url缓存失效,从而导致浏览器缓存利用率降低。

    对于上述第二条缺陷,可以举个例子来看说明:

    • 假设网站有两个页面A和B
    • A页面使用了a,b,c,d四个资源
    • B页面使用了a,b,e,f四个资源
    • 如果使用combo服务,我们会得:
      • A页面的资源引用为:/??a,b,c,d
      • B页面的资源引用为:/??a,b,e,f
    • 两个页面引用的资源是不同的url,因此浏览器会请求两个合并后的资源文件,跨页面访问没能很好的利用a、b这两个资源的缓存。

    很明显,如果combo服务能聪明的知道A页面使用的资源引用为 /??a,b 和 /??c,d,而B页面使用的资源引用为 /??a,b 和 /??e,f就好了。这样当用户在访问A页面之后再访问B页面时,只需要下载B页面的第二个combo文件即可,第一个文件已经在访问A页面时缓存好了的。

    基于这样的思考,我们在资源表上新增了一个字段,取名为 pkg,就是资源合并生成的新资源,表的结构会变成:

    {
        "res" : {
            "widget/a/a.css" : "/widget/a/a_1688c82.css",
            "widget/a/a.js"  : "/widget/a/a_ac3123s.js",
            "widget/b/b.css" : "/widget/b/b_52923ed.css",
            "widget/b/b.js"  : "/widget/b/b_a5cd123.js",
            "widget/c/c.css" : "/widget/c/c_03cab13.css",
            "widget/c/c.js"  : "/widget/c/c_bf0ae3f.js",
            "jquery.js"      : "/jquery_9151577.js",
            "bootstrap.css"  : "/bootstrap_f5ba12d.css",
            "bootstrap.js"   : "/bootstrap_a0b3ef9.js"
        },
        "pkg" : {
            "p0" : {
                "url" : "/pkg/lib_cef213d.js",
                "has" : [ "jquery.js", "bootstrap.js" ]
            },
            "p1" : {
                "url" : "/pkg/lib_afec33f.css",
                "has" : [ "bootstrap.css" ]
            },
            "p2" : {
                "url" : "/pkg/widgets_22feac1.js",
                "has" : [
                    "widget/a/a.js",
                    "widget/b/b.js",
                    "widget/c/c.js"
                ]
            },
            "p3" : {
                "url" : "/pkg/widgets_af23ce5.css",
                "has" : [
                    "widget/a/a.css",
                    "widget/b/b.css",
                    "widget/c/c.css"
                ]
            }
        }
    }

    相比之前的表,可以看到新表中多了一个pkg字段,并且记录了打包后的文件所包含的独立资源。这样,我们重新设计一下 require_static、load_widget 这两个模板接口,实现这样的逻辑:

    在查表的时候,如果一个静态资源有pkg字段,那么就去加载pkg字段所指向的打包文件,否则加载资源本身。

    比如执行require_static('bootstrap.js'),查表得知bootstrap.js被打包在了p1中,因此取出p1包的url /pkg/lib_cef213d.js,并且记录页面已加载了 jquery.jsbootstrap.js 两个资源。这样一来,之前的模板代码执行之后得到的html就变成了:

    <html>
    <head>
        <title>page</title>
        <link rel="stylesheet" type="text/css" href="/pkg/lib_afec33f.css"/>
        <link rel="stylesheet" type="text/css" href="/pkg/widgets_af23ce5.css"/>
    </head>
    <body>
        <div> content of module a </div>
        <div> content of module b </div>
        <div> content of module c </div>
        <script type="text/javascript" src="/pkg/lib_cef213d.js"></script>
        <script type="text/javascript" src="/pkg/widgets_22feac1.js"></script>
    </body>
    </html>

    虽然这种策略请求有4个,不如combo形式的请求少,但可能在统计上是性能更好的方案。由于两个lib打包的文件修改的可能性很小,因此这两个请求的缓存利用率会非常高,每次项目发布后,用户需要重新下载的静态资源可能要比combo请求节省很多带宽。

    性能优化既是一个工程问题,又是一个统计问题。优化性能时如果只关注一个页面的首次加载是很片面的。还应该考虑全站页面间跳转、项目迭代后更新资源等情况下的优化策略。

    此时,我们又引入了一个新的问题:如何决定哪些文件被打包?

    从经验来看,项目初期可以采用人工配置的方式来指定打包情况,比如:

    {
        "pack" : {
            "lib.js"      : [ "jquery.js", "bootstrap.js" ],
            "lib.css"     : "bootstrap.css",
            "widgets.js"  : "widget/**.js",
            "widgets.css" : "widget/**.css"
        }
    }

    但随着系统规模的增大,人工配置会带来非常高的维护成本,此时需要一个辅助系统,通过分析线上访问日志和静态资源组合加载情况来自动生成这份配置文件,系统设计如图:

    静态资源分析系统

    至此,我们通过基于表的静态资源管理系统和三个模板接口实现了几个重要的性能优化原则,现在我们再来回顾一下前面的性能优化原则分类表,剔除掉已经做到了的,看看还剩下哪些没做到的:

    优化方向 优化手段
    请求数量 拆分初始化负载
    缓存利用 使Ajax可缓存
    页面结构 尽早刷新文档的输出

    拆分初始化负载 的目标是将页面一开始加载时不需要执行的资源从所有资源中分离出来,等到需要的时候再加载。工程师通常没有耐心去区分资源的分类情况,但我们可以利用组件化框架接口来帮助工程师管理资源的使用。还是从例子开始思考,如果我们有一个js文件是用户交互后才需要加载的,会怎样呢:

    <html>
    <head>
        <title>page</title>
        <?php require_static('jquery.js'); ?>
        <?php require_static('bootstrap.css'); ?>
        <?php require_static('bootstrap.js'); ?>
        <!--[ CSS LINKS PLACEHOLDER ]-->
    </head>
    <body>
        <?php load_widget('a'); ?>
        <?php load_widget('b'); ?>
        <?php load_widget('c'); ?>
    
        <?php script('start'); ?>
        <script>
            $(document.body).click(function(){
                require.async('dialog.js', function(dialog){
                    dialog.show('you catch me!');
                });
            });
        </script>
        <?php script('end'); ?>
    
        <!--[ SCRIPTS PLACEHOLDER ]-->
    </body>
    </html>

    很明显,dialog.js 这个文件我们不需要在初始化的时候就加载,因此它应该在后续的交互中再加载,但文件都加了md5戳,我们如何能在浏览器环境中知道加载的url呢?

    答案就是:把静态资源表的一部分输出在页面上,供前端模块化框架加载静态资源。

    我就不多解释代码的执行过程了,大家看到完整的html输出就能理解是怎么回事了:

    <html>
    <head>
        <title>page</title>
        <link rel="stylesheet" type="text/css" href="/pkg/lib_afec33f.css"/>
        <link rel="stylesheet" type="text/css" href="/pkg/widgets_af23ce5.css"/>
    </head>
    <body>
        <div> content of module a </div>
        <div> content of module b </div>
        <div> content of module c </div>
        <script type="text/javascript" src="/pkg/lib_cef213d.js"></script>
        <script type="text/javascript" src="/pkg/widgets_22feac1.js"></script>
        <script>
            //将静态资源表输出在前端页面中
            require.config({
                res : {
                    'dialog.js' : '/dialog_fa3df03.js'
                }
            });
        </script>
        <script>
            $(document.body).click(function(){
                //require.async接口查表确定加载资源的url
                require.async('dialog.js', function(dialog){
                    dialog.show('you catch me!');
                });
            });
        </script>
    </body>
    </html>

    dialog.js不会在页面以script src的形式输出,而是变成了资源注册,这样,当页面点击触发require.async执行的时候,async函数才会查表找到资源的url并加载它,加载完毕后触发回调函数。

    以上框架示例我实现了一个java-jsp版的,有兴趣的同学请看这里:https://github.com/fouber/fis-java-jsp

    到目前为止,我们又以架构的形式实现了一项优化原则(拆分初始化负载),回顾我们的优化分类表,现在仅有两项没能做到了:

    优化方向 优化手段
    缓存利用 使Ajax可缓存
    页面结构 尽早刷新文档的输出

    剩下的两项优化原则要做到并不容易,真正可缓存的Ajax在现实开发中比较少见,而 尽早刷新文档的输出 原则facebook在2010年的velocity上 提到过,就是BigPipe技术。当时facebook团队还讲到了Quickling和PageCache两项技术,其中的PageCache算是比较彻底的实现Ajax可缓存的优化原则了。由于篇幅关系,就不在此展开了,后续还会撰文详细解读这两项技术。

    总结

    其实在前端开发工程管理领域还有很多细节值得探索和挖掘,提升前端团队生产力水平并不是一句空话,它需要我们能对前端开发及代码运行有更深刻的认识,对性能优化原则有更细致的分析与研究。在前端工业化开发的所有环节均有可节省的人力成本,这些成本非常可观,相信现在很多大型互联网公司也都有了这样的共识。

    本文只是将这个领域中很小的一部分知识的展开讨论,抛砖引玉,希望能为业界相关领域的工作者提供一些不一样的思路。

移动web问题小结

    Meta标签:

    这个想必大家都知道,当页面在手机上显示时,增加这个meta可以让页面强制让文档的宽度与设备的宽度保持1:1,并且文档最大的宽度比例是1.0,且不允许用户点击屏幕放大浏览。

    这两个属性分别对ios上自动识别电话和android上自动识别邮箱做了限制。

     

     获取滚动条的值:

    桌面浏览器中想要获取滚动条的值是通过document.scrollTop和document.scrollLeft得到的,但在iOS中你会发现这两个属性是未定义的,为什么呢?因为在iOS中没有滚动条的概念,在Android中通过这两个属性可以正常获取到滚动条的值,那么在iOS中我们该如何获取滚动条的值呢?就是上面两个属性,但是事实证明android也支持这属性,所以索性都用woindow.scroll.

     

    禁止选择文本:

    禁止用户选择文本,ios和android都支持

     

    屏蔽阴影:

    亲测,可以同时屏蔽输入框怪异的内阴影,解决iOS下无法修改按钮样式,测试还发现一个小问题就是,加了上面的属性后,iOS下默认还是带有圆角的,不过可以使用 border-radius属性修改。

     

     css之border-box:

    那我想要一个元素100%显示,又必须有一个固定的padding-left/padding-right,还有1px的边框,怎么办?这样编写代码必然导致出现横向滚动条,肿么办?要相信问题就是用来解决的。这时候伟大的css3为我们提供了box-sizing属性,对于这个属性的具体解释不做赘述(想深入了解的同学可以到w3school查看,要知道自己动手会更容易记忆)。让我们看看如何解决上面的问题:

     

     css3多文本换行:

    Webkit支持一个名为-webkit-line-clamp的属性,参见链接,也就是说这个属性并不是标准的一部分,可能是Webkit内部使用的,或者被弃用的属性。需要注意的是display需要设置成box,-webkit-line-clamp表示需要显示几行。

     

     Retina屏幕高清图片:

    image-set的语法,类似于不同的文本,图像也会显示成不同的:

    1.  不支持image-set:在不支持image-set的浏览器下,他会支持background-image图像,也就是说不支持image-set的浏览器下,他们解析background-image中的背景图像;
    2.  支持image-set:如果你的浏览器支持image-sete,而且是普通显屏下,此时浏览器会选择image-set中的@1x背景图像;
    3.  Retina屏幕下的image-set:如果你的浏览器支持image-set,而且是在Retina屏幕下,此时浏览器会选择image-set中的@2x背景图像。

     

     

     html5重力感应事件:

    关于deviceMotionEvent是HTML5新增的事件,用来检测手机重力感应效果具体可参考http://w3c.github.io/deviceorientation/spec-source-orientation.html

     

    移动端touch事件:

    • touchstart //当手指接触屏幕时触发
    • touchmove //当已经接触屏幕的手指开始移动后触发
    • touchend //当手指离开屏幕时触发
    • touchcancel//当某种touch事件非正常结束时触发

    这4个事件的触发顺序为:

    touchstart -> touchmove ->  touchend ->touchcancel

    对于某些android系统touch的bug:

    比如手指在屏幕由上向下拖动页面时,理论上是会触发 一个 touchstart ,很多次 touchmove ,和最终的 touchend ,可是在android 4.0上,touchmove只被触发一次,触发时间和touchstart 差不多,而touchend直接没有被触发。这是一个非常严重的bug,在google Issue已有不少人提出 ,这个很蛋疼的bug是在模拟下拉刷新是遇到的尤其当touchmove的dom节点数量变多时比出现,当时解决办法就是用settimeout来稀释touchmove。

     

    单击延迟:

    click 事件因为要等待双击确认,会有 300ms 的延迟,体验并不是很好。

    开发者大多数会使用封装的 tap 事件来代替click 事件,所谓的 tap 事件由 touchstart 事件 + touchmove 判断 + touchend 事件封装组成。

    Creating Fast Buttons for Mobile Web Applications

    Eliminate 300ms delay on click events in mobile Safari

     

    IOS里面fixed的文本框焦点居中

    在ios里面,当一个文本框的样式为fixed时候,如果这个文本框获得焦点,它的位置就会乱掉,由于ios里面做了自适应居中,这个fixed的文本框会跑到页面中间。类似:

     

    解决办法有两个:

    可以在文本框获得焦点的时候将fixed改为absolute,失去焦点时在改回fixed,但是这样会让屏幕有上下滑动的体验不太好。

     

    还有一种就是用一个假的fixed的文本框放在页面顶部,一个absolute的文本框隐藏在页面顶部,当fixed的文本框获得焦点时候将其隐藏,然后显示absolute的文本框,当失去焦点时,在把absolute的文本框隐藏,fixed的文本框显示。

     

    最后一种就是顶部的input不参与滚动,只让其下面滚动。

     

    position:sticky

    position:sticky是一个新的css3属性,它的表现类似position:relative和position:fixed的合体,在目标区域在屏幕中可见时,它的行为就像position:relative; 而当页面滚动超出目标区域时,它的表现就像position:fixed,它会固定在目标位置。

    浏览器兼容性

    由于这是一个全新的属性,以至于到现在都没有一个规范,W3C也刚刚开始讨论它,而现在只有webkit nightly版本和chrome 开发版(Chrome 23.0.1247.0+ Canary)才开始支持它。

    另外需要注意的是,如果同时定义了left和right值,那么left生效,right会无效,同样,同时定义了top和bottom,top赢~~

    移动端点透事件

    简单的说,由于在移动端我们经常会使用tap(touchstart)事件来替换掉click事件,那么就会有一种场景是:

    div是绝对定位的蒙层z-index高于a,而a标签是页面中的一个链接,我们给div绑定tap事件:

    我们点击蒙层时 div正常消失,但是当我们在a标签上点击蒙层时,发现a链接被触发,这就是所谓的点透事件。

    原因:

    touchstart 早于 touchend 早于 click。亦即click的触发是有延迟的,这个时间大概在300ms左右,也就是说我们tap触发之后蒙层隐藏,此时click还没有触发,300ms之后由于蒙层隐藏,我们的click触发到了下面的a链接上。

    解决办法:

    1 尽量都使用touch事件来替换click事件。

    2 阻止a链接的click的preventDefault

     

    base64编码图片替换url图片

    u在移动端,网络请求是很珍贵的资源,尤其在2g或者3g网络下,所以能不发请求的资源都尽量不要发,对于一些小图片icon之类的,可以将图片用base64编码,来减少网络请求。

     

    手机拍照和上传图片

    <input type=”file”>的accept 属性

     

    动画效果时开启硬件加速

    我们在制作动画效果时经常会想要改版元素的top或者left来让元素动起来,在pc端还好但是移动端就会有较大的卡顿感,这么我们需要使用css3的  transform: translate3d;来替换,

    此效果可以让浏览器开启gpu加速,渲染更流畅,但是笔着实验时在ios上体验良好,但在一些低端android机型可能会出现意想不到的效果。

     

    快速回弹滚动

    在iOS上如果你想让一个元素拥有像 Native 的滚动效果,你可以这样做:

    经笔着测试,此效果在不同的ios系统表现不一致,

    对于局部滚动,ios8以上,不加此效果,滚动的超级慢,ios8一下,不加此效果,滚动还算比较流畅

    对于body滚动,ios8以上,不加此效果同样拥有弹性滚动效果。

     

    持续更新中。。

    ios和android局部滚动时隐藏原生滚动条

    android

    ios

    使用一个稍微高一些div包裹住这个有滚动条的div然后设置overflow:hidden挡住之

    设置placeholder时候 focus时候文字没有隐藏

    移动端不同的input对应不同的键盘展示样式

    ios —- android

    type email

    type url

    type tel

    type search

    未完待续

    参考资料:http://www.nihaoshijie.com.cn/index.php/archives/455

    原创文章转载请注明:

    转载自AlloyTeam:http://www.alloyteam.com/2015/06/yi-dong-web-wen-ti-xiao-jie/

1/3 1 2 3