前言

我们做前端开发的时候,很有可能会做一个竞品分析,比如我就做过去哪儿、艺龙、同程等与携程的移动站点竞品分析,竞品分析的目的一般是技术对比,但是更多的是业务对比,知己知彼,百战不殆;我们同时会借鉴、学习其它网站的技术,比如网站HTML使用、class命名、使用了什么新技术,还有优化体验相关的,对大型网站的学习分析是对自己网站提高的借鉴,也是个人能力的提升途径,今天我们就来一起学习下天猫的移动站点。

PS:此文单独学习借鉴,不涉及其它,请相关同事不要在意,文中有误请提出。

打开站点首页http://www.tmall.com/,一个站点映入眼帘:

一般情况下网站加载很快,文档加载结束在200ms左右,我们看一个网站首先会看他是否遵循web标准,所谓web标准不是那么绝对,简单来说HTML、JS、CSS各干各的,并且不要犯一些低级错误,比如标签闭合、标签名小写什么的,但是当我进入第二个页面却发现一个不好的地方。

DOCTYPE不顶行

当我点击天猫精选与品牌墙时,发现其中的源文件有一个问题:

可以看到,这个doctype没有顶行,我为什么会关注这个呢,因为携程现在的站点是采用的.net,.net会在cshtml第一行写一个using XXX之类的服务器端脚本,我们在grunt打包的时候没有压缩,然后一个页面的表现十分怪异,header里面一部分html代码跑到了下面,我开始以为是有标签没有闭合,或者有标签嵌套错误导致,调了好久才发现是doctype没顶行写,这个时候页面会按照怪异模式解析,导致了莫名其妙的问题。

再看天猫超市频道:

可能因为是php的,导致页面生成的有点怪,但是这些问题应该在发布时候做html压缩。

SEO相关

从源文件与最后生成dom来说,天猫不太注重SEO,这个是阿里与百度角力所致,这种不做SEO的站点尤其适合做webapp,但是我们看到天猫依旧采用的多页的模式,可能是出于成本或者webapp不成熟考虑吧。

由于这里没有SEO需求,我们不在这里多做纠缠,但是我注意到了另外一个问题:

移动站点未使用section、header等html5标签,是因为要考虑低版本兼容,或者没有seo需求觉得这样做意义不大呢?这个不可预知。

300ms延迟

一般移动站点会有300ms延迟问题,我特地去试了点击一个按钮,响应十分迅速,这个一般是两种解决方案:

① fastclick

② tap

我们跟进一个按钮试试看,比如这个分类按钮:

  1. <a href="javascript:void(0);" target="_self" id="J_CategoryTrigger" class="category-trigger">分类</a>

从这个点击其实可以看到一些天猫团队的素质,就简单说下这个J_CategoryTrigger钩子,因为我是做单页应用的,所以一般会将事件钩子放到class里面,这里放到id里面的,其实要移植到class里面也相当容易,这样的意义是dom结构可能变化,但是我钩子却是不变的,这个对前端样式升级会提供好处,这里扯的有点远,我们继续深入这个按钮,最后在这里发现了调用点:

这里我们还意外收获到一个信息,天猫是依赖与kissy的,kissy是阿里的一套前端框架,里面有很多组件和工具类,可惜我还没来得急拜读,这里只能瞎子摸象了。

  1. a.on("click tap",
  2. function(a) {
  3. i.show();
  4. e.later(function() {
  5. i.addClass("category-dialog-unfold")
  6. },
  7. 10);
  8. t.fadeIn(.2)
  9. })

可以看到这个框架里面应该封装了类似jQuery/Zepto之类的dom库,这里如此的绑定了事件,再深入我们不管,但是我认为这里还是直接使用fastclick来的好,编码时候便不用写tap这类事件模拟了。

这里获得的第二个信息是,天猫团队是采用了模块加载的,同样也是依赖kissy的:

  1. KISSY.add("fp-m/mods/category", function(e, a, r) {
  2. var i = e.one("#J_CategoryDialog");
  3. var t = e.one("#J_CategoryMask");
  4. var n = {init: function() {
  5. var e = this;
  6. e._initCategoryTrigger();
  7. e._initCategoryClose()
  8. },_initCategoryTrigger: function() {
  9. var a = e.one("#J_CategoryTrigger");
  10. if (!i || !t || !a)
  11. return;
  12. a.on("click tap", function(a) {
  13. i.show();
  14. e.later(function() {
  15. i.addClass("category-dialog-unfold")
  16. }, 10);
  17. t.fadeIn(.2)
  18. })
  19. },_initCategoryClose: function() {
  20. var a = e.one("#J_CategoryClose");
  21. if (!i || !t || !a)
  22. return;
  23. a.on("click tap", function(e) {
  24. e.halt();
  25. i.removeClass("category-dialog-unfold");
  26. t.fadeOut(.2)
  27. })
  28. },_loginHandler: function() {
  29. }};
  30. return n
  31. }

虽然并未使用kissy,但是一套框架完成这么多事情,我觉得是不是kissy对于移动端来说可能有点笨重,这个问题的答案是:

第二个应该是kissy的核心库,感觉还行,具体还得深入了解kissy才行,这里不多说,其中一段代码我非常感兴趣:

  1. KISSY.add("combobox/combobox-xtpl", [], function() {
  2. return function(f) {
  3. var a, d = this;
  4. a = this.config.utils;
  5. var j = a.runBlockCommand, k = a.renderOutput, g = a.getProperty, h = a.runInlineCommand, e = a.getPropertyOrRunCommand;
  6. a = '<div id="ks-combobox-invalid-el-';
  7. var b = e(d, f, {}, "id", 0, 1);
  8. a += k(b, !0);
  9. a += '"\n class="';
  10. var b = {}, c = [];
  11. c.push("invalid-el");
  12. b.params = c;
  13. b = h(d, f, b, "getBaseCssClasses", 2);
  14. a += k(b, !0);
  15. a += '">\n <div class="';
  16. b = {};
  17. c = [];
  18. c.push("invalid-inner");
  19. b.params = c;
  20. b = h(d, f, b, "getBaseCssClasses", 3);
  21. a += k(b,
  22. !0);
  23. a += '"></div>\n</div>\n\n';
  24. var b = {}, c = [], m = g(d, f, "hasTrigger", 0, 6);
  25. c.push(m);
  26. b.params = c;
  27. b.fn = function(b) {
  28. var a;
  29. a = '\n<div id="ks-combobox-trigger-';
  30. var c = e(d, b, {}, "id", 0, 7);
  31. a += k(c, !0);
  32. a += '"\n class="';
  33. var c = {}, g = [];
  34. g.push("trigger");
  35. c.params = g;
  36. c = h(d, b, c, "getBaseCssClasses", 8);
  37. a += k(c, !0);
  38. a += '">\n <div class="';
  39. c = {};
  40. g = [];
  41. g.push("trigger-inner");
  42. c.params = g;
  43. b = h(d, b, c, "getBaseCssClasses", 9);
  44. a += k(b, !0);
  45. return a + '">▼</div>\n</div>\n'
  46. };
  47. a += j(d, f, b, "if", 6);
  48. a += '\n\n<div class="';
  49. b = {};
  50. c = [];
  51. c.push("input-wrap");
  52. b.params = c;
  53. b = h(d, f, b, "getBaseCssClasses", 13);
  54. a += k(b, !0);
  55. a += '">\n\n <input id="ks-combobox-input-';
  56. b = e(d, f, {}, "id", 0, 15);
  57. a += k(b, !0);
  58. a += '"\n aria-haspopup="true"\n aria-autocomplete="list"\n aria-haspopup="true"\n role="autocomplete"\n aria-expanded="false"\n\n ';
  59. b = {};
  60. c = [];
  61. m = g(d, f, "disabled", 0, 22);
  62. c.push(m);
  63. b.params = c;
  64. b.fn = function() {
  65. return "\n disabled\n "
  66. };
  67. a += j(d, f, b, "if", 22);
  68. a += '\n\n autocomplete="off"\n class="';
  69. b = {};
  70. c = [];
  71. c.push("input");
  72. b.params = c;
  73. b = h(d, f, b, "getBaseCssClasses", 27);
  74. a += k(b, !0);
  75. a += '"\n\n value="';
  76. b = e(d, f, {}, "value", 0, 29);
  77. a += k(b, !0);
  78. a += '"\n />\n\n\n <label id="ks-combobox-placeholder-';
  79. b = e(d, f, {}, "id", 0, 33);
  80. a += k(b, !0);
  81. a += '"\n for="ks-combobox-input-';
  82. b = e(d, f, {}, "id", 0, 34);
  83. a += k(b, !0);
  84. a += "\"\n style='display:";
  85. b = {};
  86. c = [];
  87. g = g(d, f, "value", 0, 35);
  88. c.push(g);
  89. b.params = c;
  90. b.fn = function() {
  91. return "none"
  92. };
  93. b.inverse = function() {
  94. return "block"
  95. };
  96. a += j(d, f, b, "if", 35);
  97. a += ";'\n class=\"";
  98. j = {};
  99. g = [];
  100. g.push("placeholder");
  101. j.params = g;
  102. j = h(d, f, j, "getBaseCssClasses", 36);
  103. a += k(j, !0);
  104. a += '">\n ';
  105. f = e(d, f, {}, "placeholder", 0, 37);
  106. a += k(f, !0);
  107. return a + "\n </label>\n</div>\n"
  108. }
  109. });
  1. KISSY.add("component/control/render-xtpl", [], function() {
  2. return function(f) {
  3. var c, g = this;
  4. c = this.config.utils;
  5. var k = c.runBlockCommand, m = c.renderOutput, h = c.getProperty, e = c.runInlineCommand, i = c.getPropertyOrRunCommand;
  6. c = '<div id="';
  7. var d = i(g, f, {}, "id", 0, 1);
  8. c += m(d, !0);
  9. c += '"\n class="';
  10. var d = {}, n = [];
  11. n.push("");
  12. d.params = n;
  13. e = e(g, f, d, "getBaseCssClasses", 2);
  14. c += m(e, !0);
  15. c += "\n";
  16. e = {};
  17. d = [];
  18. n = h(g, f, "elCls", 0, 3);
  19. d.push(n);
  20. e.params = d;
  21. e.fn = function(a) {
  22. var b;
  23. b = "\n ";
  24. a = i(g, a, {}, ".", 0, 4);
  25. b += m(a, !0);
  26. return b + " \n"
  27. };
  28. c +=
  29. k(g, f, e, "each", 3);
  30. c += '\n"\n\n';
  31. e = {};
  32. d = [];
  33. n = h(g, f, "elAttrs", 0, 8);
  34. d.push(n);
  35. e.params = d;
  36. e.fn = function(a) {
  37. var b;
  38. b = " \n ";
  39. var c = i(g, a, {}, "xindex", 0, 9);
  40. b += m(c, !0);
  41. b += '="';
  42. a = i(g, a, {}, ".", 0, 9);
  43. b += m(a, !0);
  44. return b + '"\n'
  45. };
  46. c += k(g, f, e, "each", 8);
  47. c += '\n\nstyle="\n';
  48. e = {};
  49. d = [];
  50. h = h(g, f, "elStyle", 0, 13);
  51. d.push(h);
  52. e.params = d;
  53. e.fn = function(a) {
  54. var b;
  55. b = " \n ";
  56. var c = i(g, a, {}, "xindex", 0, 14);
  57. b += m(c, !0);
  58. b += ":";
  59. a = i(g, a, {}, ".", 0, 14);
  60. b += m(a, !0);
  61. return b + ";\n"
  62. };
  63. c += k(g, f, e, "each", 13);
  64. return c + '\n">'
  65. }
  66. });

可以看到,这个应该是html模块化的东西,以underscore的模板引擎来说是这样的:

  1. <div><span>我是:</span><%=name%></div>
  1. var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};
  2. with(obj||{}){
  3. __p+='<div><span>我是:</span>'+
  4. ((__t=(name))==null?'':__t)+
  5. '</div>';
  6. }
  7. return __p;

我们会有一个预编译操作,将对应的模块文件转为下面这种AMD规范模式,这样做可能会使体积有一丝丝的增加,但是却可以绕过一次javascript编译,对手机的执行效率以及电池的耗损都有好处,而这一工作一般是配合grunt在发布前完成的。

但是,天猫或者说kissy的做法,由于代码是压缩的,我有点看不出深浅,希望不是在拼接字符串吧。

PS:这里说300ms延迟扯得有点远。

层级关系

一般来说一个站点的z-index应该由js开发与css同时设计,但是阿里的规则是必须同时get javascript与css两项技能,所以这个zindex可能是自己规划的,首先这里的图片轮播导航条跑到了侧边栏上面:

这里视觉上脱离文档流的元素有:

① 图片轮播导航

  1. .slide_1425130247393-631fader-nav-div {
  2. display: inline-block;
  3. position: absolute;
  4. bottom: 6px;
  5. left: 12px;
  6. padding:;
  7. z-index:;
  8. }

② 侧边栏

③ 侧边栏隶属的mask蒙版

④ 最下面的导航条

但是真实场景与我预料的却大不一样,他的导航条是relative的,然后里面的元素全部是absolute的......

说实话,因为我不是专业的CSS,这里有点看不出深浅,但是relative的话,我要是页面有resize操作,可能要出问题,比如:

最后统计站点的几个关键z-index值:

① 轮播图片导航白点 absolute zIndex:10

② 侧边栏包裹层 relative zIndex:4

③ 侧边栏内部元素 absolute zIndex:100

④ 广告栏 fixed zIndex:9999

这个在zIndex应该是没有规划的,我再看看后面一个页面的弹出层:

absolute,zIndex为100

absolute 在zIndex:9999

经过观察我得到一个结论,天猫全站弹出层z-index未做规划,这个在多页应用中问题不大,但是一旦采用webapp模式或者伪单页模式,弹出层一多便容易出问题,戒之慎之。

规范化事件

天猫站点我觉得另外一个有问题的地方是,事件未被统一化,比如上面的弹出层弹出后有一个关闭按钮,那么他的事件绑定在哪呢?

这里的dom结构是:

  1. <a href="javascript:void(0);" id="J_CategoryClose" class="category-close" target="_self" data-spm-anchor-id="875.7403452.0.0">关闭</a>
  1. _initCategoryClose: function() {
  2. var a = e.one("#J_CategoryClose");
  3. if (!i || !t || !a) return;
  4. a.on("click tap",
  5. function(e) {
  6. e.halt();
  7. i.removeClass("category-dialog-unfold");
  8. t.fadeOut(.2)
  9. })
  10. }

可以看到,这里的事件绑定依旧在采用on、bind之类的做法,其实这种方式应该摒弃,每个模块都可以看成一个组件,在模块show后,统一将事件点代理到根元素,比如这样:

  1. events: {
  2. 'click selector': function() {}
    //......
  3. }

这样的话,效果好得多,不必显示的时候绑定事件,消失的时候移除事件什么的。

另外一个体验上的问题是,这个侧边栏我觉得应该采用局部滚动方式fixed布局,采用类似IScroll类方案,体验可能会更好,这里点击蒙版关闭组件的操作也应该有。

这块有点太细了,我们再看看其它地方,比如非常常用的图片轮播组件。

图片轮播

天猫的图片轮播组件,采用的也是transform的方式做移动,传统的是采用移动left,这种方式基本被摒弃。

  1. transform: translate(-1600px, 0px)

全站的图片都是做了延迟加载的,但是就图片轮播组件这里的延迟加载却让我有点不理解了,请看dom结构:

可以看到,他是图片滑到对应index索引位置才动态的将img标签插入进去,而上面的导航一致在重绘,如果网络比较慢的话就会出现这种情况:

对的,因为节点已经生成,出来了一个白屏的项目,其实这里可以加上延迟加载那个图标的,便不会出现白屏。

PS:为什么我这里关注的这么清楚呢,因为我这块也没做最近被业务团队提了需求......

其次图片轮播组件与下面用到的这个模块可以统一:

轮播组件继承他稍作扩展即可,上面关注点基本聚焦到了一些细节上,再看看其它部分。

结语

今天的观察还是过于细节化,停留在表面,加之家里装备不足,没能将天猫的精髓看到,我们接下来几天再观察下,看看是否能观察出天猫的性能处理方案,今天太晚了,暂时到此。

文中有何不足或者错误请您指正

浅析天猫H5站点的更多相关文章

  1. 【web前端面试题整理08】说说最近几次面试(水)

    为什么换工作 换工作简单来讲一般会归纳为钱不够或者人不对,我们团队氛围很不错,所以基本就定位到钱不够了,而我更多是考虑到以后的职业发展,简单说来就是对以后几年的工作有想法,而这种想法实现不一定能在现在 ...

  2. 【前端优化之渲染优化】大屏android手机动画丢帧的背后

    前言 上周我与阿里的宇果有一次技术的交流,然后对天猫H5站点做了一些浅层次的分析,后面点时间基本天天都会有联系,中途聊了一些技术细节.聊了双方团队在干什么,最后聊到了前端优化.因为我本身参与了几次携程 ...

  3. 【移动前端开发实践】从无到有(统计、请求、MVC、模块化)H5开发须知

    前言 不知不觉来百度已有半年之久,这半年是996的半年,是孤军奋战的半年,是跌跌撞撞的半年,一个字:真的是累死人啦! 我所进入的团队相当于公司内部创业团队,人员基本全部是新招的,最初开发时连数据库都没 ...

  4. 用H5开发微信还是开发APP?

    用H5开发微信还是开发APP? 随着技术的飞速发展,HTML第五版技术标准的更新,在移动端,由于其相对较低的开发成本及强大的跨平台运行能力,越来越多的信息型产品也开始选择这样轻量级的H5页面进行快速迭 ...

  5. 浅谈Hybrid技术的设计与实现

    前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 随着移动浪潮的兴起,各种APP层出不穷,极速的业务扩展提升了团队对开发 ...

  6. 【blade利刃出鞘】一起进入移动端webapp开发吧

    前言 在移动浪潮袭来的时候,小钗有幸进入框架组做webapp框架开发,过程中遇到了移动端的各种坑,也产生了各种激情,就我们公司的发展历程来说 第一阶段:使用传统方式开发移动站点,少量引入HTML5元素 ...

  7. 【初探IONIC】不会Native可不可以开发APP?

    前言 Hybrid技术流行已经有一段日子了,楼主的关注点也一直围绕着移动端围绕着Hybrid相关展开,Hybrid已经是大大提升开发效率的开发方式了,但是仍然需要至少一个IOS与Andriod,那么可 ...

  8. 谈一谈前端多容器(多webview平台)处理方案

    文中是我个人的一些开发经验,希望对各位有用,也希望各位多多支持讨论,指出文中不足以及提出您的一些建议. 双容器 得益于近几年移动端的发展,前端早已今非昔比,从大型框架来说angularJS.react ...

  9. 【大前端之前后分离01】JS前端渲染VS服务器端渲染

    前言 之前看了一篇文章:@Charlie.Zheng Web系统开发构架再思考-前后端的完全分离,文中论述了为何要前后分离,站在前端的角度来看,是很有必要的:但是如何说服团队使用前端渲染方案却是一个现 ...

随机推荐

  1. 解决:win10_x64 VMware Workstation and Hyper-V are not compatible. Remove the Hyper-V role from the system before running VMware Workstation

    bcdedit /set hypervisorlaunchtype off A reboot of of the Windows OS is necessary  必须重启才能生效   To enab ...

  2. linux字符串url编码与解码

    编码的两种方式 echo '手机' | tr -d '\n' | xxd -plain | sed 's/\(..\)/%\1/g' echo '手机' |tr -d '\n' |od -An -tx ...

  3. 跨平台的 .NET 运行环境 Mono 3.2 新特性

    Mono 3.2 发布了,对 Mono 3.0 和 2.10 版本的支持不再继续,而且这两个分支也不再提供 bug 修复更新. Mono 3.2 主要新特性: LLVM 更新到 3.2 版本,带来更多 ...

  4. MonoTouch 二三事(三)mono mkbundle 打包程序的解包支持

    2014.10.06 更新 编写了 IDA Pro 的插件,用来解包和打包 mkbundle程序,请参见 https://github.com/binsys/MKBundleManager 许久以后, ...

  5. 冗余代码都走开——前端模块打包利器 Rollup.js 入门

    之前翻译过一篇文章,介绍了通过 ES2015 的解构赋值语法引入模块,可以让打包工具(browserify)最终编译出来的代码量最小化. 殊不知在 webpack 1.X 版本是无法利用该特性来避免引 ...

  6. 巧用 mask-image 实现简单进度加载界面

    最近给 nzoo 折腾官网,拿 angular2.0 + webpack 实现SPA,然后觉得最终打包后的出口文件有点大,用户首次访问会有一个时间较长的白屏等候界面,感觉体验不太好. 于是希望在用户下 ...

  7. 写给.NET开发者的数据库Migration方案

    微软给我们提供了一种非常好用的数据库迁移方案,但是我发现周围的同学用的并不多,所以我还是想把这个方案整理一下..NET选手看过来,特别是还在通过手工执行脚本来迁移数据库的同学们,当然你也可以选择EF的 ...

  8. [数据库基础]——图解JOIN

    阅读导航 一.概要 二.JOIN分类 三.JOIN分类详解 一.概要 JOIN对于接触过数据库的人,这个词都不陌生,而且很多人很清楚各种JOIN,还有很多人对这个理解也不是很透彻,这次就说说JOIN操 ...

  9. ABP(现代ASP.NET样板开发框架)系列之15、ABP应用层——应用服务(Application services)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之15.ABP应用层——应用服务(Application services) ABP是“ASP.NET Boiler ...

  10. ECMASCript2015 提案 stage-3的对象展开运算符

    看源码时看到如下的代码 export default { //通过mapActions将actions映射到methods里 methods: { ...mapActions([ 'updateSta ...