[转]浅析天猫H5站点

前言

我们做前端开发的时候,很有可能会做一个竞品分析,比如我就做过去哪儿、艺龙、同程等与携程的移动站点竞品分析,竞品分析的目的一般是技术对比,但是更多的是业务对比,知己知彼,百战不殆;我们同时会借鉴、学习其它网站的技术,比如网站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

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

<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的模板引擎来说是这样的:

<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可能是自己规划的,首先这里的图片轮播导航条跑到了侧边栏上面:

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

① 图片轮播导航

.slide_1425130247393-631fader-nav-div {
display: inline-block;
position: absolute;
bottom: 6px;
left: 12px;
padding: 0;
z-index: 10;
}

② 侧边栏

③ 侧边栏隶属的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结构是:

<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后,统一将事件点代理到根元素,比如这样:

events: {
  ‘click selector‘: function() {}  //......
}

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

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

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

图片轮播

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

transform: translate(-1600px, 0px)

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

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

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

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

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

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

结语

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

原文链接

时间: 2024-11-08 23:48:13

[转]浅析天猫H5站点的相关文章

浅析天猫H5站点

前言 我们做前端开发的时候,很有可能会做一个竞品分析,比如我就做过去哪儿.艺龙.同程等与携程的移动站点竞品分析,竞品分析的目的一般是技术对比,但是更多的是业务对比,知己知彼,百战不殆:我们同时会借鉴.学习其它网站的技术,比如网站HTML使用.class命名.使用了什么新技术,还有优化体验相关的,对大型网站的学习分析是对自己网站提高的借鉴,也是个人能力的提升途径,今天我们就来一起学习下天猫的移动站点. PS:此文单独学习借鉴,不涉及其它,请相关同事不要在意,文中有误请提出. 打开站点首页http:

浅析网络h5棋牌神兽大厅游戏用户群体

Q1446595067官网:h5.haozibbs.com如今,棋牌游戏在全国任何一个游戏平台上都是相当的火爆,几乎每一个网络棋牌开发公司都有一款甚至多款棋牌游戏.摩申网络棋牌开发商就一直致力于棋牌游戏的开发,紧跟棋牌游戏的发展潮流.而棋牌游戏的流行,不仅归功于网络宽带和智能手机的普及,还要归功于大众对于娱乐要求和生活质量的提高.而棋牌游戏用户群体的结构变化,使得棋牌游戏的用户群有着独特的特点. 网络棋牌游戏比一般的网络游戏有着更多的用户数量,而且数量稳定,在线时间长.棋牌游戏不需要过度的专业技

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

前言 上周我与阿里的宇果有一次技术的交流,然后对天猫H5站点做了一些浅层次的分析,后面点时间基本天天都会有联系,中途聊了一些技术细节.聊了双方团队在干什么,最后聊到了前端优化.因为我本身参与了几次携程H5站点的优化,在这方面有一些心得,但是与宇果交流的过程中发现我们在优化的时候忽略了一些细节. 携程做优化的时候整个重心基本放到了尺寸的缩减,和宇果的交流过程中他提出了渲染优化,其实渲染优化无非是减少回流,对于减少回流我们也有一些概念,我一直认为这个事情应该业务开发关注而不是框架关注(事实上框架也无

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

为什么换工作 换工作简单来讲一般会归纳为钱不够或者人不对,我们团队氛围很不错,所以基本就定位到钱不够了,而我更多是考虑到以后的职业发展,简单说来就是对以后几年的工作有想法,而这种想法实现不一定能在现在的团队获得,在短期内也看不到希望,加之公司职级晋升不合理等考虑,也就自然而然想到了离职. 其实在鞋厂这两年,真的收获了很多东西,也负责了很重要的业务,这些财富可能是其它大公司不一定能给予的,虽然一直级别低点也就没太多在意,直到最近职级福利缩水...... 最初我面试的职级为X,HR给了一套智力题做,

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

前言 不知不觉来百度已有半年之久,这半年是996的半年,是孤军奋战的半年,是跌跌撞撞的半年,一个字:真的是累死人啦! 我所进入的团队相当于公司内部创业团队,人员基本全部是新招的,最初开发时连数据库都没设计,当时评审需求的时候居然有一个产品经理拿了一份他设计的数据库,当时我作为一个前端就惊呆了...... 最初的前端只有我1人,这事实上与我想来学习学习的愿望是背道而驰的,但既然来都来了也只能独挑大梁,马上投入开发,当时涉及的项目有: ① H5站点 ② PC站点 ③ Mis后台管理系统 ④ 各种百度

第八章 交互技术,8.3 2016双11前端突破(作者:天猫前端团队)

8.3 2016双11前端突破 前言 2016 年天猫前端相比去年有了非常多不同维度的突破,主要可以分为四大类大类: 稳定性.监控 极致的性能优化 业务创新 / 平台建设 技术创新 / 互动 1. 稳定性.监控 商品到每个用户浏览的每个环节都有监控,尤其在针对消费者体验上的 TES,让前端在消费者真实浏览的过程当中也能够有更进一步的分析在不同环境下消费者实际的体验.以及从服务器 Wormhole 渲染层进行了一系列的稳定性.监控. 1.1 Wormhole双11会场稳定性保障 Wormhole承

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

用H5开发微信还是开发APP? 随着技术的飞速发展,HTML第五版技术标准的更新,在移动端,由于其相对较低的开发成本及强大的跨平台运行能力,越来越多的信息型产品也开始选择这样轻量级的H5页面进行快速迭代,同时借用微信等平台快速触达用户. 如今App的红利时期早己消失殆尽,大家下载应用的热情已经不再像刚开始那么火热,再加上那么多烧钱BAT产品大佬培养的用户习惯,没点补贴很难吸引用户.直接在应用市场推App的成本也很高,这时web的优势就体现出来了,它很轻,迭代还快,而且现在有微信这么好的入口.坐拥

浅谈移动前端的最佳实践(转)

前言 这几天,第三轮全站优化结束,测试项目在2G首屏载入速度取得了一些优化成绩,对比下来有10s左右的差距: 这次优化工作结束后,已经是第三次大规模折腾公司框架了,这里将一些自己知道的移动端的建议提出来分享下,希望对各位有用 文中有误请您提出,以免误人自误 技术选型 单页or多页 spa(single page application)也就是我们常常说的web应用程序webapp,被认为是业内的发展趋势,主要有两个优点: ① 用户体验好 ② 可以更好的降低服务器压力 但是单页有几个致命的缺点:

ipad&amp;mobile通用webapp框架前哨战

响应式设计的意义 随着移动设备的发展,移动设备以迅猛的势头分刮着PC的占有率,ipad或者android pad的市场占有率稳步提升,所以我们的程序需要在ipad上很好的运行,对于公司来说有以下负担:设备系统上来说主要分为android ios:尺寸上看又以手机与pad为一个分界线,如果再加一个H5站点,其开发所投入资源不可谓不小! Hybrid的出现,解决了大部分问题,针对尺寸上的问题有一种东西叫做响应式设计,这个响应式设计似乎可以解决我们的问题,所以今天我就来告诉大家什么是响应式设计,或者说