标签: webkit

  • 悲催的Android Webview——记新版广告墙开发

    悲催的Android Webview——记新版广告墙开发

    前一阵很辛劳,所以荒废了博客。前几天终于完成了这项艰苦卓绝的工程:HTML5版广告墙,决定写篇文章,记录一下踩过的坑。

    项目介绍

    广告墙属于典型的列表式应用:打开后是无尽列表,通过滑动手指驱使列表滚动,上拉加载更多内容,下拉刷新列表(这次没做)。单击列表中的某项,打开详细页;单击上面的后退按钮,退回上一级页面。

    考虑到目标平台是Android系统,内嵌WebKit内核,我采用了时下流行的HTML5+CSS3技术,并且做好了向下兼容的准备。为了减少代码量,我放弃了jQuery,也没有考虑半调子Zepto,全部使用原生开发。操作上,开始想用Hammer.js封装手势,后来处理滚动效果处理得不好,最终选择了iScroll 5。模板方面,前端使用Handlebars.js,后端使用Mustache,所以编译时还写了段代码做转换。

    项目代码托管在github上,因为前端不涉及到业务逻辑,所以仓库是公开的,有兴趣的同学以clone下来看看。

    技术选型大概聊这些,接下来开始进入正题。

    性能vs兼容性

    实际上,随着硬件的发展,对于列表式这种非性能敏感性应用来说,性能早已不是制约其发展的首要因素了。另一方面,有一定专业精神的前端如我,平时也会注重积累各种性能优化的点,并不断在项目中实验、实施——所以我就很搞不明白,技术实力强大如jQuery,为啥会弄出个jQuery Mobile这种完全不能用的东西出来……

    相反,在这个项目中,由于平台分裂带来的兼容性问题成了最大的绊脚石。尤其是这些兼容性问题,极少找到现成的解决方案……下面举两个例子:

    Vivo X909 和z-index

    正常情况下,position:absolute;的dom节点在不显式设置z-index属性区分层级的时候,默认后面的节点会显示在前面的节点上方。大多数浏览器里都是这样,可是Vivo X909就不行。

    列表大家都知道,点击某项,详情会从右边滚动进来。由于没有设置-webkit-tap-highlight-color(现在想想应该做的,下周回去加上吧),所以这个bug当时的的表现是“单击没有反应”。这种情况很难调试,我开始以为JS有错,试了很久,后来配置好Weinre(见上篇博客),才找到症结所在。最后把几个图层分别加上z-index,问题解决。

    早期Android的animation-fill-mode

    我们仍有20%的用户在使用2.3.x版本的Android,所以这块市场必然不能放弃。在小米2上调是好的动画,在老HTC G10上,滑出的图层在动画结束后会消失。这次好在有了上次的经验,直接上Weinre;加上caniuse.com也有明确的描述,所以很快解决了。


    各种难以定位、缺少文档、找不到解决方案的兼容性问题层出不穷,让我不禁感慨:本以为买上了WebKit的康庄大道,谁知尼玛这豆腐渣工程下面全是坑啊……

    滚屏

    前面提到,开始时间比较富裕,我也比较自信,想借助Hammer.js,慢慢打磨出色的滚动体验。后来无尽的bug环伺左右,实在无法专心研究,便退而求其次,使用iScroll了。

    iScroll如今已经发展到5,其作者自信的表示:“我尽全力提升了其在Android平台上的表现,我相信现在它已经达到了极致。”实验效果确实很理想,无论是高配置的小米,还是老旧的HTC G10,都表现出色。将来有空的时候得好好研究下他是怎么做的。

    工程实践

    随着时代发展,Web开发这种脚本语言也开始分裂成开发、部署两个阶段,这是个好事情。核心代码需要为维护、测试留下足够的空间,势必在性能表现上有所不足,部署时根据应用场景的不同输出不同的结果正好能解决这个问题。这个项目中我使用Grunt,花费了不少精力在编写Gruntfile.js上。

    开发时,我将测试用的数据,按照设计好的数据结构写在define.js中,生产环境中用服务器生成真实数据来替代。不同的应用场景,也用其配置。如今,目标支持的6个场景都能稳定工作,效果不错。

    Windows下不好配ruby,使用SASS是个问题,不过后来找到了Prepros,问题迎刃而解。

    技术细节分享

    简单构造选择器

    jQuery功能确实强大,不过实在太重型了;备胎Zepto轻便一些,不过功能也弱一些,语法做不到全兼容,用起来不舒服。仔细考虑这个项目的需求,无非是:找到某个元素,给它添加事件侦听;或者判断它是否包含某个class,那么自己写一个直接到Dom节点的选择器就足够了。后来看到篇文章,里面详细测试了querySelectorgetElementById众的性能,发现还是后者大大领先——尤其在移动平台上,于是基本放弃了用前者的念头。

    这个选择器实现起来很容易:

        var $ = window.$ = function (selector, root) {
          root = root || document;
          var dom;
          switch (selector.charAt(0)) {
            case '#':
              selector = selector.substr(1);
              dom = document.getElementById(selector);
              break;
    
            case '.':
              selector = selector.substr(1);
              dom = root.getElementsByClassName(selector)[0];
              break;
    
            default :
              dom = root.getElementsByTagName(selector)[0];
              break;
          }
          return dom;
        };
    

    另外,$还可以用来当命名空间,保存一些需要全局使用的变量和方法。

    使用Handlebars.js生成预编译模板

    预编译模版可以降低运行时的运算量,对于移动开发这种能省则省的应用场景,实在是必备之物。另一方面,为了方便开发,把模版放在HTML里更好调整。好在有“编译输出”这个步骤,可以两全其美。

    Handlebars.js的预编译是把模版转换成根据数据生成HTML的JS代码,可以使用uglify压缩。编译之前最好过滤掉多余的空格和换行符,这样生成的JS文件也会小很多。

    当然,再预编译也不如直接渲染HTML来的快,所以在服务器端生成页面的时候,一定要包含实际内容,让用户尽快得到信息。而PHP没有现成的Handlebars实现(我也不打算写),好在有mustache.php,并且handlebars兼容mustache(其实不完全,也被坑了),所以我写了一小段代码将Handlebars模版转换成Mustache模版

    使用attr()实现CSS中的多语言

    除了传统的中文版,这次还要制作英文版。英文版与中文版的功能几乎完全一致,只是文字全部换成英文。最简单的做法就是搞个en.html——因为使用了模版,JS里不包含任何文字内容;但是“上拉加载更多”这个功能目前使用CSS的:after实现——初衷是不修改dom,不引起relayout——当然,反正我用着Sass,增加个语言包不难,不过我还是希望把修改集中在一个地方。

    后来查了下CSS表达式的兼容性,发现Android 2.1起就支持,那就好办了,先把各阶段文案放到HTML的属性里:

        // 中文
        <div id="list" data-normal="继续上拉,加载更多广告" data-more="松开加载" data-loading="加载中..." data-no-more="没有其它广告了,再看看吧" data-error="加载失败,请稍候重试" data-over=""></div>
        // 英文
        <div id="list" data-normal="Pull up to load" data-more="Release to load" data-loading="Loading..." data-no-more="No more ads" data-error="Load failed" data-over=""></div>
    

    然后在CSS里使用attr()访问即可

        #list.over
          &:after
            content: attr(data-over)
    
        #list.more
          &:after
            content: attr(data-more)
    
        #list.loading
          &:after
            content: attr(data-loading)
    
        #list.no-more
          &:after
            content: attr(data-no-more)
    
        #list.error
          &:after
            content: attr(data-error)
    

    WebKit中z-indextranslateZ(0)混用,导致click事件半失灵的解决

    1. 要在浏览器里触发下载,只要链接到一个浏览器打不开的文件就行。链接跳转是在click触发的浏览器内建行为,换言之,链接跳转依赖于click的正常触发。
    2. clicktouch event晚200~300ms,所以为了保证用户体验,我们通常用taptouchstart + touchend)来替代click响应用户操作
    3. 所以一个“点击下载按钮”的操作,就会先响应tap事件,弹出详情浮层,告知用户获得积分的最终条件,然后等待浏览器开始下载
    4. CSS动画最好使用translateZ(0)促使浏览器启用GPU加速
    5. 这个bug在以上条件下产生,影响这个过程

    之前我在PC版Chrome里也曾见识过这个bug(最新版没验证,大概20+的时候吧)。具体表现为,一个层,它的translateZ有赋值,它可能被移到某处(通过translateXtranslateY,或者动画过程中),不过还未到达那里,但是它仍然会拦截那里mousedown/mouseup事件,导致click事件无法正常触发。

    这个bug非常难排查,因为它并不影响tap;视觉上看不到那个层,会想当然的认为不应该跟那个层有关系;并且,WTF,如果按得时间稍长,比如半秒,就可以触发click……因为触发条件比较复杂,我甚至很长时间没找到规律。最后只好请出“小黄鸭调试法”,试图向索隆解释发生了什么,然后发现无法正常下载的按钮都是被详情浮层遮盖着,继而回忆起以前解决过类似问题。

    最后解决这个问题的方案我其实不很满意,不过确实能顺利工作:

    1. 层的当前位置不会覆盖按钮,先不给层增加动画
    2. tap后,给click留出足够的时间(我这里用400ms),再给层添加动画
    3. 确保click可以响应,通过setTimeout给层添加动画——本来300ms就测试通过,不过为了保险,我还是决定写成400

    仍然悬而未决的问题

    虽说项目已经快上线了,但仍有几个不解之谜……

    1. 某些手机仍然无法下载(启动<a>
    2. 快速拖动的时候可能把内容拖走(应该是触发了浏览器默认行为)

    总结

    做完项目回头看,系统分裂造成的兼容性问题比想象中多很多,原先以为最多是有些2.x版本不支持,做向下兼容就好,谁知还有各种实现细节问题。不过,在PC Chrome + 手机Chrome + Weinrealert之后,只要有耐心,大多数问题时可以解决的。

    说完开发效率,另一方面再来看看产品表现。我们对性能不需要太乐观,也不用太悲观,敏感型应用,老老实实用原生开发,或编译成原生应用;非敏感型,跨平台高兼容性(咦,这眼泪哪里来的)的优势很明显。通过我实际看到移动端的表现,我们这些页面仔应该更有信心,将来移动大市场里,Web App的份额绝对不比Native App要少。

    这项事业任重而道远,需要我们一起努力。

  • 解决 iOS webkit 使用CSS动画时闪烁的问题

    解决 iOS webkit 使用CSS动画时闪烁的问题

    这个,咱们必须承认版本管理不是万能的,尤其对于像我这样习惯不好的人来说,更是如此。比如,上次不知道改了什么东西,导致肉大师制作的杂志在 iOS 里突然就变卡了,而且不仅卡,还伴随黑块、切换图片的最后会闪一下。

    开始我一直以为是性能问题,调啊调啊调啊,就是不见好。于是 Google “ios phonegap 闪烁”,然后发现一篇文章,内容是解决 iOS 下 Safari 渲染 Transition 时页面闪动的问题。说只要加一句 -webkit-backface-visibility: hidden; 就行,这样可以避免元素转换时显示元素背面。虽然听起来莫名其妙,不过我觉得有戏,但是文章里没有提应该加在哪儿,所以我想了想,在样式表里加了一句:

    #container {
      -webkit-backface-visibility: hidden;
    }
    

    因为我用到 iScroll,而负责动画的CSS是加在 #container 上,所以我就理所当然的把这段代码加在这里。测试,没有效果。

    于是我一发狠,把这段样式加在所有元素上,居然问题就解决了。

    #viewport * {
      -webkit-backface-visibility: hidden;
    }
    
  • Google新产品风格滚动条的实现方法

    Google全线产品改版后,褒贬不一。不过它们滚动条看起来很漂亮,简单好看,有点Ubuntu的感觉。

    不过实现起来并不复杂,研究之后,发现是这样定义CSS的:

    ::-webkit-scrollbar{
      width:6px;
    }
    ::-webkit-scrollbar-track-piece{
      background:none;
    }
    ::-webkit-scrollbar-thumb {
      background:#DCD9DE;
      border-radius:6px;
    }
    ::-webkit-scrollbar-thumb:hover {
      background:#EAE6EA;
    }

    当然,看到”-webkit“,自然只有webkit内核的浏览器比如Chrome才生效(我没有测试Safari)。具体支持哪些样式暂时还不确定,Google搜索“-webkit-scrollbar”竟然没有结果。已知background、border这些都没问题,:hover这样的伪类也支持,margin、padding似乎没有作用。以后再慢慢研究吧。