当前位置:千赢国际官网 > 千赢网页手机版登入 > 浅谈移动前端的最佳实践,玩法知多少

浅谈移动前端的最佳实践,玩法知多少

文章作者:千赢网页手机版登入 上传时间:2019-10-05

前端相关数据监控

2015/08/16 · HTML5 · 数据监控

原文出处: AlloyTeam   

项目开发完成外发后,没有一个监控系统,我们很难了解到发布出去的代码在用户机器上执行是否正确,所以需要建立前端代码性能相关的监控系统。

所以我们需要做以下的一些模块:

一、收集脚本执行错误

JavaScript

function error(msg,url,line){ var REPORT_URL = "xxxx/cgi"; // 收集上报数据的信息 var m =[msg, url, line, navigator.userAgent, new Date];// 收集错误信息,发生错误的脚本文件网络地址,用户代理信息,时间 var url = REPORT_URL m.join('||');// 组装错误上报信息内容URL var img = new Image; img.onload = img.onerror = function(){ img = null; }; img.src = url;// 发送数据到后台cgi } // 监听错误上报 window.onerror = function(msg,url,line){ error(msg,url,line); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function error(msg,url,line){
   var REPORT_URL = "xxxx/cgi"; // 收集上报数据的信息
   var m =[msg, url, line, navigator.userAgent, new Date];// 收集错误信息,发生错误的脚本文件网络地址,用户代理信息,时间
   var url = REPORT_URL m.join('||');// 组装错误上报信息内容URL
   var img = new Image;
   img.onload = img.onerror = function(){
      img = null;
   };
   img.src = url;// 发送数据到后台cgi
}
// 监听错误上报
window.onerror = function(msg,url,line){
   error(msg,url,line);
}

通过管理后台系统,我们可以看到页面上每次错误的信息,通过这些信息我们可以很快定位并且解决问题。

二、收集html5 performance信息

performance 包含页面加载到执行完成的整个生命周期中不同执行步骤的执行时间。performance相关文章点击如下:使用performance API 监测页面性能

计算不同步骤时间相对于navigationStart的时间差,可以通过相应后台CGI收集。

JavaScript

function _performance(){ var REPORT_URL = "xxxx/cgi?perf="; var perf = (window.webkitPerformance ? window.webkitPerformance : window.msPerformance ), points = ['navigationStart','unloadEventStart','unloadEventEnd','redirectStart','redirectEnd','fetchStart','domainLookupStart','connectStart','requestStart','responseStart','responseEnd','domLoading','domInteractive','domContentLoadedEventEnd','domComplete','loadEventStart','loadEventEnd']; var timing = pref.timing; perf = perf ? perf : window.performance; if( perf && timing ) { var arr = []; var navigationStart = timing[points[0]]; for(var i=0,l=points.length;i<l;i ){ arr[i] = timing[points[i]] - navigationStart; } var url = REPORT_URL arr.join("-"); var img = new Image; img.onload = img.onerror = function(){ img=null; } img.src = url; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function _performance(){
   var REPORT_URL = "xxxx/cgi?perf=";
   var perf = (window.webkitPerformance ? window.webkitPerformance : window.msPerformance ),
      points = ['navigationStart','unloadEventStart','unloadEventEnd','redirectStart','redirectEnd','fetchStart','domainLookupStart','connectStart','requestStart','responseStart','responseEnd','domLoading','domInteractive','domContentLoadedEventEnd','domComplete','loadEventStart','loadEventEnd'];
   var timing = pref.timing;
   perf = perf  ? perf : window.performance;
   if( perf  && timing ) {
      var arr = [];
      var navigationStart = timing[points[0]];
      for(var i=0,l=points.length;i<l;i ){
         arr[i] = timing[points[i]] - navigationStart;
      }
   var url = REPORT_URL arr.join("-");
   var img = new Image;
   img.onload = img.onerror = function(){
      img=null;
   }
   img.src = url;
}

通过后台接口收集和统计,我们可以对页面执行性能有很详细的了解。

三、统计每个页面的JS和CSS加载时间

在JS或者CSS加载之前打上时间戳,加载之后打上时间戳,并且将数据上报到后台。加载时间反映了页面白屏,可操作的等待时间。

XHTML

<script>var cssLoadStart = new Date</script> <link rel="stylesheet" href="xxx.css" type="text/css" media="all"> <link rel="stylesheet" href="xxx1.css" type="text/css" media="all"> <link rel="stylesheet" href="xxx2.css" type="text/css" media="all"> <sript> var cssLoadTime = ( new Date) - cssLoadStart; var jsLoadStart = new Date; </script> <script type="text/javascript" src="xx1.js"></script> <script type="text/javascript" src="xx2.js"></script> <script type="text/javascript" src="xx3.js"></script> <script> var jsLoadTime = ( new Date) - jsLoadStart; var REPORT_URL = 'xxx/cgi?data=' var img = new Image; img.onload = img.onerror = function(){ img = null; }; img.src = REPORT_URL cssLoadTime '-' jsLoadTime; </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>var cssLoadStart = new Date</script>
<link rel="stylesheet" href="xxx.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx1.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx2.css" type="text/css" media="all">
<sript>
   var cssLoadTime = ( new Date) - cssLoadStart;
   var jsLoadStart = new Date;
</script>
<script type="text/javascript" src="xx1.js"></script>
<script type="text/javascript" src="xx2.js"></script>
<script type="text/javascript" src="xx3.js"></script>
<script>
   var jsLoadTime = ( new Date) - jsLoadStart;
   var REPORT_URL = 'xxx/cgi?data='
   var img = new Image;
   img.onload = img.onerror = function(){
      img = null;
   };
   img.src = REPORT_URL cssLoadTime '-' jsLoadTime;
</script>

XHTML

<a href="" target="_blank"> </a>

1
<a href="https://github.com/perltzhu/js-data-report" target="_blank"> </a>

H5 玩法知多少

2017/09/01 · HTML5 · 1 评论 · H5

原文出处: 大熊   

html5的发布让移动端web增添了很多新的能力,这些能力给我们带来了很多新的玩法,不知你知道了多少呢?下面我将结合案例罗列一些自H5发布以来出现的新玩法,给大家温故而知新。本文也适合大家在策划H5活动的时候作为参考,说不定在这儿就找到灵感了。

构思H5的玩法该从何入手呢?网上的H5五花八门,其玩法大多都可以归纳到基于传感器、基于触摸屏操作、基于画面呈现、基于内容这四类来考虑。它们既有基于其中一类来构思玩法,又有将多个类别组合起来创造更复杂丰富的玩法。与H5新能力相关的是前三种类型,本文也会从这三种类型进行展开,分别介绍一下各类型下有什么玩法,而基于内容的玩法主要是图文混排展示内容,答题类游戏等与内容强相关的玩法,实际上他们也会多多少少与另外三类搭上点关系的。

接下来你将会看到这些玩法:

基于传感器:

类别 玩法或适用场景
陀螺仪、重力感应 体感游戏、摇一摇、全景图/AR转换角度、转动手机制造视差效果
地理位置 提供周边生活服务、与运动结合、上班打卡功能、外勤人员的工作监控、类似QQ的AR红包和pokemon go玩法、展示附近的优惠和广告
人脸识别 体感游戏、将用户样貌与节日/游戏/电影等主题元素相结合进行宣传的场景、用户样貌与明星脸相似度测试、猜用户年龄、猜情绪、模拟化妆
webRTC 视频会议、视频聊天、在线教育、在线问诊等视频/音频通讯的场景

基于触摸屏操作:

类别 玩法或适用场景
全景交互 虚拟全景展示、身临其境的实景展示或活动现场展示、商品360度展示
多屏互动 朋友间合作/竞技游戏、你画我猜、一问一答、情侣互动、多个屏幕拼起来看视频、投屏应用、线下现场观众互动
单屏滚动 公司主页、产品介绍、报告总结、邀请函等展示类场景
手势操作 商品图片放大查看场景、图片拖拽/旋转等编辑场景、手势解锁、拼图游戏

基于画面呈现:

类别 玩法或适用场景
视频/动画展示 产品、节日、游戏、电影等宣传场景;内容比较丰富的场景
图片裁剪和形变 类似变形金刚、七巧板形式的图片转换效果展示场景

 

如果以场景为维度来展开推荐玩法,则可参考下图所示:

图片 1图片 2

接着我会逐一介绍一下这些玩法,读者也可以直接跳到感兴趣的部分去阅读。除上表外还有一些目前还不太能用的H5能力,如手机震动、光线传感器、距离传感器等,由于边幅原因,暂不作展开介绍,读者可以自行查找相关资料。部分案例如没有贴上二维码,是因为该案例已下线了。

浅谈移动前端的最佳实践

2015/07/13 · HTML5, JavaScript · 移动前端

原文出处: 叶小钗(@欲苍穹)   

参考资料:

  • html5 performance en
  • html5 performance cn
  • javascript onerror api

    1 赞 1 收藏 评论

图片 3

基于传感器

这类玩法依靠手机上的传感器实现,这些传感器有陀螺仪、gps、摄像头、麦克风、震动传感器、光线传感器、距离传感器等。大家在设计玩法的时候,要结合业务本身选用相关的合适的传感器,不要随意搭配或随意叠加多个传感器,避免画蛇添足。例如做周边生活类的需求,就应该选用gps传感器获取地理位置,做全景图的需求就选用陀螺仪来提供便捷的交互。

 

前言

这几天,第三轮全站优化结束,测试项目在2G首屏载入速度取得了一些优化成绩,对比下来有10s左右的差距:

图片 4

这次优化工作结束后,已经是第三次大规模折腾公司框架了,这里将一些自己知道的移动端的建议提出来分享下,希望对各位有用

文中有误请您提出,以免误人自误

陀螺仪、重力感应

这类交互在体感游戏中比较常见,如控制射箭的方向、挥剑、打乒乓球等,而在H5中则可用于摇一摇、控制赛车左右前进、检查手机是否平躺/竖直、全景图/AR转换角度等,也可以用于制造视差效果(如王者荣耀的登录界面),使画面富有层次感。

案例:

降温摇可乐:

该H5在模拟摇可乐的情景,玩家需要不停地摇动手机,尽快使可乐喷出来。个人认为可以换另一种玩法,让朋友互相传递手机摇,谁摇爆了可乐就算输。

图片 5图片 6

来玩点耐心吧:

这是腾讯互动娱乐2017年度发布会的预热H5,提倡用户做事要多点耐心,因而玩法是耐心地竖立手机,看着一幅漂亮繁复的画慢慢地画完。

图片 7图片 8

图片 9图片 10

图片 11

图片 12

一“陆”狂飙,极速挑战:

该H5与速度与激情8联动,先播放一段在朋友圈上飚车的视频,然后开始赛车游戏,通过左右倾侧手机来控制赛车左右前进、躲避障碍,最后根据行使的距离进行排名和抽奖。

图片 13

图片 14

天猫:地球上的另一个你

该H5同时播放代表理想与现实的两个视频,利用重力感应来切换看到的画面,向上转动手机模拟抬头看到理想的画面,向下转动手机模拟低头看到现实的画面,也可以转到一半的位置同时看两个视频同时播放,比较有新意,也较好表达出理想和现实之间的对比。

图片 15

图片 16

 

技术选型

地理位置

这类H5结合用户所处的位置,可以提供比较方便的周边生活服务,如查找附近的摩拜单车、获取附近的餐饮信息和前往路线;也可以与运动结合,如记录用户的跑步轨迹。如果用于办公,则可以做上班打卡功能、外勤人员的工作监控等。如果用于游戏,则可以创造出类似pokemon go的玩法,在用户的位置附近散落奖品,让用户走到目的地收集奖励。

案例:

杜蕾斯全民抓喜鹊:

该游戏H5类似pokemon go,在地图上散落各种喜鹊,用户需走到散落点附近,将喜鹊划入篮圈内,然后点击捕获,捕获够一定数量就可以兑换奖品。

图片 17

 

单页or多页

spa(single page application)也就是我们常常说的web应用程序webapp,被认为是业内的发展趋势,主要有两个优点:

① 用户体验好

② 可以更好的降低服务器压力

但是单页有几个致命的缺点:

① SEO支持不好,往往需要单独写程序处理SEO问题

② webapp本身的内存管理难,Javascript、Css非常容易互相影响

当然,这里不是说多页便不能有好的用户体验,不能降低服务器压力;多页也会有变量污染的问题发生,但造成webapp依旧是“发展趋势”,而没有大规模应用的主要原因是:

webapp模式门槛较高,很容易玩坏

1
webapp模式门槛较高,很容易玩坏

其实webapp的最大问题与上述几点没有关系,实际上阻碍webapp的是技术门槛与手机性能,硬件方面不必多说,这里主要说技术门槛。

webapp做的好,可以玩动画,可以玩真正意义上的预加载,可以玩无缝页面切换,从某些方面甚至可以媲美原生APP,这也是webapp受到追捧的原因。

但是,以上很容易被玩坏!因为webapp模式不可避免的需要用到框架,站点需要一个切实可行的控制器来管理History以及页面view实例化工作,于是大家会选择诸如:

Backbone、angularJS、canJs之类的MVC框架,于是整个前端的技术要求被平白无故的提升了一个等级,原来操作dom可以做的事情,现在不一定能做了。

很多人对以上框架只停留在使用层面,几轮培训后,对底层往往感到一头雾水,就算开发了几个项目后,仍然还是只能了解View层面的东西;有对技术感兴趣的同事会慢慢了解底层,但多数仍然只关注业务开发,这个时候网站体验便会受到影响,还让webapp受到质疑。

所以这里建议是:

① 精英团队在公司有钱并且网站周期在两年以上的话可以选用webapp模式

② 一般团队还是使用多页吧,坑不了

③ 更好的建议是参考下改变后的新浪微博,采用伪单页模式,将网站分为几个模块做到组件化开发,碰到差距较大的页面便刷新也无不可

PS:事实上webapp模式的网站体验确实会好一点

人脸识别

顾名思义,就是用人脸和H5进行互动,玩法有根据人脸猜测年龄、猜情绪,测试与明星脸的匹配度,将人脸和游戏电影人物相结合,将人脸变成小时候的样子,根据人脸的动作做出反馈(如张嘴时从嘴里飞出企鹅、眨眼睛拍照)等,通常跟AR和图片合成技术搭配使用。这类方式比较适合用于游戏电影宣传、个性化用户样貌、图片类产品推广的场景。合成的图片可以在边角加上活动二维码,使得其他用户看到这张图片时也可以参与活动。

相关技术主要是人脸识别和人脸动作捕捉的技术,腾讯有提供优图识别技术,微软也有提供人脸识别技术。

案例:

腾讯:我的魔兽我主演

这个H5将人脸和魔兽电影相结合。用户上传自己的照片,选择喜欢的模板和添加一些刀疤之类的装饰后,就可以生成一张用自己脸制作出来的的魔兽海报了。

图片 18

图片 19

我的小学生证件照:

用户上传照片生成小学生证件照,并可以通过”换基因“换一下生成的样子。

图片 20

图片 21

QQ钱包三周年活动之扫脸获红包:

用户在活动页张大嘴巴说”FUN开付“后,会有企鹅从嘴里探出来跳舞,然后发红包给用户。

图片 22

图片 23

 

框架选择

移动前端依旧离不开框架,而且框架呈变化状态,以我厂为例,我们几轮框架选型是:

① 多页应用 jQuery

② jQuery mobile(这个坑谁用谁知道)

③ 开始webapp模式(jQuery requireJS Backbone underscore)

④ 瘦身(zepto requireJS Backbone View部分 underscore)

……

移动大潮来临后,浏览器基本的兼容得到了保证,所以完整的jQuery变得不是那么必须,因为尺寸原因,所以一般被zepto替换,zepto与jQuery有什么差异呢?

webRTC

webRTC是H5的一个新特性,它可以在web上访问摄像头和麦克风,进行视频和音频的实时通讯,用途有视频会议、视频聊天、在线教育、在线问诊等,以前只能客户端才能实现的视频类应用也可以应用到web上了。兼容性方面,移动端浏览器的支持程度很差,安卓5.x以上的chrome才支持,而ios直至safari11发布才终于得到了支持,但这个发展表明web端也逐渐具备这个能力了,我们可以预先想想这方面的策划,说不定不久的将来就能用上了。

案例:

Chatroulette:

这是一个随机视频聊天网页,你可以随机和地球上的陌生人视频聊天,一般遇到的都是外国人,我也是从这个网页第一次真实地感受到真的有外国人的存在。可惜现在不能访问了。

图片 24

 

jQuery VS Zepto

首先,Zepto与jQuery的API大体相似,但是实现细节上差异甚大,我们使用Zepto一般完成两个操作:

① dom操作

② ajax处理

但是我们知道HTML5提供了一个document.querySelectorAll的接口,可以解决我们90%的需求,于是jQuery的sizzle便意义不大了,后来jQuery也做了一轮优化,让用户打包时候选择,需要sizzle才用。

其次jQuery的一些属性操作上做足了兼容,比如:

JavaScript

el.css('transform', 'translate(-968px, 0px) translateZ(0px)') //jQuery会自动根据不同浏览器内核为你处理为: el.css('-webkit-transform', 'translate(-968px, 0px) translateZ(0px)')

1
2
3
el.css('transform', 'translate(-968px, 0px) translateZ(0px)')
//jQuery会自动根据不同浏览器内核为你处理为:
el.css('-webkit-transform', 'translate(-968px, 0px) translateZ(0px)')

又比如说,以下差异比比皆是:

JavaScript

el.hide(1000);//jQuery具有动画,Zepto不会鸟你

1
el.hide(1000);//jQuery具有动画,Zepto不会鸟你

然后,jQuery最初实现animate是采用js循环设置状态记录的方式,所以可以有效的记住状态暂停动画元素;Zepto的animate完全依赖于css3动画,暂停需要再想办法
图片 25 View Code
其实,我们简单从实现上就可以看出,Zepto这里是偷懒了,其实现最初就没有想考虑IE,所以winphone根本不能愉快的玩耍

图片 26

JavaScript

zepto.Z = function(dom, selector) { dom = dom || [] dom.__proto__ = $.fn dom.selector = selector || '' return dom }

1
2
3
4
5
6
zepto.Z = function(dom, selector) {
  dom = dom || []
  dom.__proto__ = $.fn
  dom.selector = selector || ''
  return dom
}

图片 27

真实的差异还有很多,我这里也没法一一列出,这里要说明的一个问题其实就是:

jQuery大而全,兼容、性能良好;Zepto针对移动端定制,一些地方缺少兼容,但是尺寸小

1
jQuery大而全,兼容、性能良好;Zepto针对移动端定制,一些地方缺少兼容,但是尺寸小

图片 28

zepto设计的目的是提供jquery的类似的APIs,不以100%覆盖jquery为目的,一个5-10k的通用库、下载并执行快、有一个熟悉通用的API,所以你能把你主要的精力放到应用开发上。

上图是1.8版本与Zepto完整版的对比,Gzip在2G情况下20K造成的差距在2-5s之间,3G情况会有1s的差距,这也是我们选择Zepto的原因,下面简单介绍下Zepto。

基于触摸屏操作:

除了利用传感器创造特别的玩法外,在触摸屏上的操作也有多种玩法,如单屏滚动、手势操作、全景交互及多屏互动。在触摸屏上的操作要符合用户的正常习惯,例如滑动屏幕可以翻页、移动场景,双指拉开表示放大操作。如果预料到用户可能不清楚如何操作,则需要提供操作示范。此外,可操作区域也要弄大些,方便用户操作,例如当前画面只是操作一只猫爪上下移动,那么除了可以在猫爪上滑动外,在其他空白区域上下滑动也应该可以让猫爪上下移动。

 

Zepto清单

模块 建议 描述
ZEPTO Core module; contains most methods

核心模块,包含初始化Zepto对象的实现,以及dom选择器、css属性操作、dom属性操作

EVENT Event handling via on() & off()

Zepto事件处理库,包含整个dom事件的实现

AJAX XMLHttpRequest and JSONP functionality

Zepto ajax模块的实现

FORM Serialize & submit web forms

form表单相关实现,可以删去,移动端来说意义不大

IE Support for Internet Explorer 10 on the desktop and Windows Phone 8

这个便是为上面那段实现还账的,几行代码将方法属性扩展至dom集合上(所以标准浏览器返回的是一个实例,ie返回的是一个加工后的数组)

DETECT  ✔ Provides $.os and $.browser information

设备判断,检测当前设备以及浏览器型号

FX  ✔ The animate() method

animate方法,这里叫fx模块有点让人摸不着头脑

FX_METHODS Animated showhidetoggle, and fade*() methods.

一些jQuery有的方法,Zepto没有的,这里做修复,比如fadeIn fadeOut意义不大

ASSETS Experimental support for cleaning up iOS memory after removing image elements from the DOM.

没有实际使用过,具体用处不明

DATA A full-blown data() method, capable of storing arbitrary objects in memory.

数据存储模块

DEFERRED Provides $.Deferred promises API. Depends on the “callbacks” module.

神奇的deferred模块,语法糖,为解决回调嵌套而生

CALLBACKS Provides $.Callbacks for use in “deferred” module.

服务于deferred,实际未使用过

SELECTOR   ✔ Experimental jQuery CSS extensions support for functionality such as$('div:first') and el.is(':visible').

扩展选择器,一些语法糖

TOUCH  X Fires tap– and swipe–related events on touch devices. This works with both touch (iOS, Android) and pointer events (Windows Phone).

提供简单手势库,这个大坑,谁用谁知道!!!几个有问题的地方:

① 事件直接绑定至document,性能浪费

② touchend时候使用settimeOut导致event参数无效,所以preventDefault无效,点透等情况也会发生

GESTURE Fires pinch gesture events on touch devices

对原生手势操作的封装

STACK Provides andSelf & end() chaining methods

语法糖,链式操作

IOS3 String.prototype.trim and Array.prototype.reduce methods (if they are missing) for compatibility with iOS 3.x.

没有用过

你真实项目时,完全可以按照需要选取模块即可,下面简单再列几个差异:

单屏滚动

这是一种很常见的交互形式。如幻灯片一样,网页上的每一页内容都是占满全屏的。当用户滑动屏幕翻下一页后,当前整个屏幕的内容都会翻走,然后再展示下一页全屏的内容。翻屏时可以加上一些转换的动画,如渐入渐出,使得翻页效果生动不单调,也可以加上重力感应,让手机在转动时产生视差效果。单屏滚动的应用场景比较广泛,很多主页、产品介绍、报告总结、邀请函都应用了这种形式。

案例:

腾讯互娱发布会邀请函:

邀请函采取单屏滚动的形式展示,同时利用重力感应,转动手机时会看到页面上的装饰图片也会跟着移动,制造视差,增添了乐趣。

图片 29

图片 30

图片 31

王者荣耀S7赛季总结:

该游戏总结报告也使用了单屏滚动的方式,展示了用户在该赛季各方面的成绩,如获得星数、本命英雄、给力搭档等。翻页时酷炫的动画效果使得这份报告更加生动有趣。该报告亦使用了重力感应,摆动手机时页内元素也会跟着摆动。

图片 32

图片 33

图片 34

 

其它差异

① selector
如上所述,Zepto的选择器只是jQuery的一个子集,但是这个子集满足我们90%的使用场景

② clone
Zepto的clone不支持事件clone,这句话的意思是dom clone后需要自己再处理事件,举个例子来说:

JavaScript

var el = $('.el'); el.on('click', function() { alert(1) })

1
2
3
4
5
var el = $('.el');
 
el.on('click', function() {
  alert(1)
})

JavaScript

//true的情况jQuery会连带dom事件拷贝,Zepto没有做这个处理 //jQuery库,点击clone的节点会打印1,Zepto不会 var el1 = el.clone(true); $('#wrap').append(el1);

1
2
3
4
5
//true的情况jQuery会连带dom事件拷贝,Zepto没有做这个处理
//jQuery库,点击clone的节点会打印1,Zepto不会
 
var el1 = el.clone(true);
$('#wrap').append(el1);

这个差异还比较好处理,现在都会使用事件代理,所以没clone事件也在没问题的……

这里简单看看细节实现:

JavaScript

clone: function (elem, dataAndEvents, deepDataAndEvents) { var i, l, srcElements, destElements, clone = elem.cloneNode(true), inPage = jQuery.contains(elem.ownerDocument, elem); // Fix IE cloning issues if (!support.noCloneChecked && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem)) { // We eschew Sizzle here for performance reasons: destElements = getAll(clone); srcElements = getAll(elem); for (i = 0, l = srcElements.length; i < l; i ) { fixInput(srcElements[i], destElements[i]); } } // Copy the events from the original to the clone if (dataAndEvents) { if (deepDataAndEvents) { srcElements = srcElements || getAll(elem); destElements = destElements || getAll(clone); for (i = 0, l = srcElements.length; i < l; i ) { cloneCopyEvent(srcElements[i], destElements[i]); } } else { cloneCopyEvent(elem, clone); } } // Preserve script evaluation history destElements = getAll(clone, "script"); if (destElements.length > 0) { setGlobalEval(destElements, !inPage && getAll(elem, "script")); } // Return the cloned set return clone; }, function cloneCopyEvent(src, dest) { var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; if (dest.nodeType !== 1) { return; } // 1. Copy private data: events, handlers, etc. if (dataPriv.hasData(src)) { pdataOld = dataPriv.access(src); pdataCur = dataPriv.set(dest, pdataOld); events = pdataOld.events; if (events) { delete pdataCur.handle; pdataCur.events = {}; for (type in events) { for (i = 0, l = events[type].length; i < l; i ) { jQuery.event.add(dest, type, events[type][i]); } } } } //

  1. Copy user data if (dataUser.hasData(src)) { udataOld = dataUser.access(src); udataCur = jQuery.extend({}, udataOld); dataUser.set(dest, udataCur); } }
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
clone: function (elem, dataAndEvents, deepDataAndEvents) {
   var i, l, srcElements, destElements,
         clone = elem.cloneNode(true),
         inPage = jQuery.contains(elem.ownerDocument, elem);
 
   // Fix IE cloning issues
   if (!support.noCloneChecked && (elem.nodeType === 1 || elem.nodeType === 11) &&
             !jQuery.isXMLDoc(elem)) {
 
     // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
     destElements = getAll(clone);
     srcElements = getAll(elem);
 
     for (i = 0, l = srcElements.length; i < l; i ) {
       fixInput(srcElements[i], destElements[i]);
     }
   }
 
   // Copy the events from the original to the clone
   if (dataAndEvents) {
     if (deepDataAndEvents) {
       srcElements = srcElements || getAll(elem);
       destElements = destElements || getAll(clone);
 
       for (i = 0, l = srcElements.length; i < l; i ) {
         cloneCopyEvent(srcElements[i], destElements[i]);
       }
     } else {
       cloneCopyEvent(elem, clone);
     }
   }
 
   // Preserve script evaluation history
   destElements = getAll(clone, "script");
   if (destElements.length > 0) {
     setGlobalEval(destElements, !inPage && getAll(elem, "script"));
   }
 
   // Return the cloned set
   return clone;
},
function cloneCopyEvent(src, dest) {
   var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
 
   if (dest.nodeType !== 1) {
     return;
   }
 
   // 1. Copy private data: events, handlers, etc.
   if (dataPriv.hasData(src)) {
     pdataOld = dataPriv.access(src);
     pdataCur = dataPriv.set(dest, pdataOld);
     events = pdataOld.events;
 
     if (events) {
       delete pdataCur.handle;
       pdataCur.events = {};
 
       for (type in events) {
         for (i = 0, l = events[type].length; i < l; i ) {
           jQuery.event.add(dest, type, events[type][i]);
         }
       }
     }
   }
 
   // 2. Copy user data
   if (dataUser.hasData(src)) {
     udataOld = dataUser.access(src);
     udataCur = jQuery.extend({}, udataOld);
 
     dataUser.set(dest, udataCur);
   }
}

JavaScript

clone: function(){ return this.map(function(){ return this.cloneNode(true) }) },

1
2
3
clone: function(){
  return this.map(function(){ return this.cloneNode(true) })
},

下面是Zepto的clone实现,我啥也不说了,为什么jQuery这么大呢,是有道理的。

③ data

Zepto的data只能存储字符串,你想存储复杂对象的话便把他先转换为字符串

④ offset

图片 35

JavaScript

el.offset() //Zepto返回 Object {left: 8, top: 8, width: 485, height: 18} //jQuery返回 Object {top: 8, left: 8}

1
2
3
4
5
6
7
el.offset()
 
//Zepto返回
Object {left: 8, top: 8, width: 485, height: 18}
 
//jQuery返回
Object {top: 8, left: 8}

图片 36

getBoundingClientRect 函数是W3C组织在第一版本的W3C CSSOM View specification草案中确定的一个标准方法,在此之前,只有IE浏览器是支持该方法的,W3C在这次草案中把它扶正成为标准。

getBoundingClientRect 方法返回的是调用该方法的元素的TextRectangle对象,该对象具有top、left、right、bottom四个属性,分别代表该元素上、左、右、下四条边界相对于浏览器窗口左上角(注意,不是文档区域的左上角)的偏移像素值。

JavaScript

offset: function(coordinates){ if (coordinates) return this.each(function(index){ var $this = $(this), coords = funcArg(this, coordinates, index, $this.offset()), parentOffset = $this.offsetParent().offset(), props = { top: coords.top - parentOffset.top, left: coords.left - parentOffset.left } if ($this.css('position') == 'static') props['position'] = 'relative' $this.css(props) }) if (this.length==0) return null var obj = this[0].getBoundingClientRect() return { left: obj.left window.pageXOffset, top: obj.top window.pageYOffset, width: Math.round(obj.width), height: Math.round(obj.height) } },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
offset: function(coordinates){
  if (coordinates) return this.each(function(index){
    var $this = $(this),
        coords = funcArg(this, coordinates, index, $this.offset()),
        parentOffset = $this.offsetParent().offset(),
        props = {
          top:  coords.top  - parentOffset.top,
          left: coords.left - parentOffset.left
        }
 
    if ($this.css('position') == 'static') props['position'] = 'relative'
    $this.css(props)
  })
  if (this.length==0) return null
  var obj = this[0].getBoundingClientRect()
  return {
    left: obj.left window.pageXOffset,
    top: obj.top window.pageYOffset,
    width: Math.round(obj.width),
    height: Math.round(obj.height)
  }
},

JavaScript

   jQuery offsetoffset: function (options) { if (arguments.length) { return options === undefined ? this : this.each(function (i) { jQuery.offset.setOffset(this, options, i); }); } var docElem, win, elem = this[0], box = { top: 0, left: 0 }, doc = elem && elem.ownerDocument; if (!doc) { return; } docElem = doc.documentElement; // Make sure it's not a disconnected DOM node if (!jQuery.contains(docElem, elem)) { return box; } // Support: BlackBerry 5, iOS 3 (original iPhone) // If we don't have gBCR, just use 0,0 rather than error if (typeof elem.getBoundingClientRect !== strundefined) { box = elem.getBoundingClientRect(); } win = getWindow(doc); return { top: box.top win.pageYOffset - docElem.clientTop, left: box.left win.pageXOffset - docElem.clientLeft }; },

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
 
 
 jQuery offsetoffset: function (options) {
  if (arguments.length) {
    return options === undefined ?
            this :
            this.each(function (i) {
              jQuery.offset.setOffset(this, options, i);
            });
  }
 
  var docElem, win,
        elem = this[0],
        box = { top: 0, left: 0 },
        doc = elem && elem.ownerDocument;
 
  if (!doc) {
    return;
  }
 
  docElem = doc.documentElement;
 
  // Make sure it's not a disconnected DOM node
  if (!jQuery.contains(docElem, elem)) {
    return box;
  }
 
  // Support: BlackBerry 5, iOS 3 (original iPhone)
  // If we don't have gBCR, just use 0,0 rather than error
  if (typeof elem.getBoundingClientRect !== strundefined) {
    box = elem.getBoundingClientRect();
  }
  win = getWindow(doc);
  return {
    top: box.top win.pageYOffset - docElem.clientTop,
    left: box.left win.pageXOffset - docElem.clientLeft
  };
},

差距不大,jQuery的更加严谨,总会做很多兼容,jQuery大是有道理的

手势操作

我们和屏幕交互,除了有点击、滑动外,还有很多手势操作,如拖拽、双指拉开放大、双指画圈旋转物体,画图形表示某个动作等。手势操作可用于放大查看图片、对图片进行拖拽/放大/旋转等编辑、手势解锁、也可以用于游戏上,如拼图游戏时拖拽、旋转拼图碎片。

在实现上,H5有一个手势操作库hammer.js,可以实现常用的手势操作。

图片 37

 

MVC框架选择

MVC框架流行的有Backbone、angularJS、reactJS、canJS等,我个人比较熟悉Backbone与canJS,近期也在整理canJS的一些笔记

首先提一下Backbone,我认为其最优秀的就是其View一块的实现,Backbone的View规范化了dom事件的使用,避免了事件滥用,避免了事件“失效”

但是Backbone的路由处理一块很弱,事实上一点用也没有,而且就算view一块的继承关系也非常难以处理,extend实现是:

JavaScript

var extend = function (protoProps, staticProps) { var parent = this; var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function () { return parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. _.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. var Surrogate = function () { this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) _.extend(child.prototype, protoProps); // Set a convenience property in case the parent's prototype is needed // later. child.__super__ = parent.prototype; return child; };

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
var extend = function (protoProps, staticProps) {
  var parent = this;
  var child;
 
  // The constructor function for the new subclass is either defined by you
  // (the "constructor" property in your `extend` definition), or defaulted
  // by us to simply call the parent's constructor.
  if (protoProps && _.has(protoProps, 'constructor')) {
    child = protoProps.constructor;
  } else {
    child = function () { return parent.apply(this, arguments); };
  }
 
  // Add static properties to the constructor function, if supplied.
  _.extend(child, parent, staticProps);
 
  // Set the prototype chain to inherit from `parent`, without calling
  // `parent`'s constructor function.
  var Surrogate = function () { this.constructor = child; };
  Surrogate.prototype = parent.prototype;
  child.prototype = new Surrogate;
 
  // Add prototype properties (instance properties) to the subclass,
  // if supplied.
  if (protoProps) _.extend(child.prototype, protoProps);
 
  // Set a convenience property in case the parent's prototype is needed
  // later.
  child.__super__ = parent.prototype;
 
  return child;
};

JavaScript

child.__super__ = parent.prototype;

1
child.__super__ = parent.prototype;

这是一段极为糟糕的设计,他是将parent原型的指向给到了类的的属性上,这里可以看做静态方法,那么我在实际使用的时候要如何使用呢?

我在内部原型链上或者实例方法一般使用this便能指向本身,但是却不能执行本类的方法,如果要使用指向构造函数我需要这么做:

JavaScript

this.constructor this.constructor.__super__

1
2
this.constructor
this.constructor.__super__

如果我这里想要执行父类的一个方法,还得关注起作用域指向,于是只能这样写

JavaScript

this.constructor.__super__.apply(this, arguments)

1
this.constructor.__super__.apply(this, arguments)

而我总是认为javascript的construct未必非常靠谱,于是整个人都不好了,所以在一轮使用后,基本便放弃Backbone了,但是Backbone优秀的一面也不能抹杀,我们可以借鉴Backbone实现一些更加适合项目的基础架子

Backbone另一个令人诟病的地方是其插件少,其实这里有点苛刻,移动端才兴起不久,webapp的项目又少,这里没有是很正常,别人的插件也未必能用的顺心。

angularJs我本身没有实际使用过,不好评价,根据一些朋友的实际使用情况可以得出一个结论:

JavaScript

规定的非常死,业务代码可保持一致,入门简单深入难,一旦出现问题,不太好改,对技术要求较高

1
规定的非常死,业务代码可保持一致,入门简单深入难,一旦出现问题,不太好改,对技术要求较高

这里各位根据实际情况选择就好,我这里的建议还是自己读懂一个MV*的框架,抽取需要的重写,像angularJS一次升级,之前的项目如何跟着升级,这些问题很头疼也很实际。

上次抱着解决webappSEO难题时候对reactJS有所接触,其源码洋洋洒洒10000行,没有一定功力与时间还是暂时不碰为好。

canJS学习成本与Backbone差不多,我这边准备出系列学习笔记,好不好后面调研再说。

总结一句:不建议直接将业务库框架直接取来使用,更不建议使用过重的业务框架,最好是能明白框架想要解决的问题,与自己项目的实际需求,自己造轮子知根知底。

基于画面呈现:

这类玩法一般就是展现一段比较酷炫有趣的画面内容,如视频、动画、特效,给用户带来视觉上的感官享受,用户也可以通过与画面内容互动,看自己想看的内容。

 

框架建议

最好给出一个小小的建议,希望对各位有用:

第三方库(基础库):

requireJS Zepto 阉割版underscore(将其中不太用到的方法去掉,主要使用模板引擎一块) Fastclick

MVC库/UI库:

建议自己写,不要太臃肿,可以抄袭,可以借鉴,不要完全拿来就用

这样出来的一套框架比较轻量级,知根知底,不会出现改不动的情况,最后提一句:不经过调研,没有实际场景在框架中玩模式,玩高级理念死得快,不要为技术而技术。

视频/动画展示

这类H5会播放一段时间较长但有趣生动的视频或动画来吸引用户关注其宣传内容。由于视频内容比较生动酷炫有趣味,以及常常有明星参与演出,用户一般不会太抗拒这样的广告,反而会喜欢点赞,甚至主动向朋友分享。有很多刷屏H5都是这个类型的,例如薛之谦XQQ动漫、韩寒X使命召唤等。这个玩法适合用于产品、节日、游戏、电影等宣传场景及叙述内容比较多的场景。

这个玩法的主要制作方式有:

1、视频。这种方式需要前期拍摄、后期使用专门的视频软件(如AE)制作,能制作出很炫的效果,但成本较高,用户通常只能被动地观看完整段视频。

2、使用canvas制作,这种方式可以制作出像以前的flash一样的动画,效果没视频酷炫,但这类动画既可以看,又可以在播放途中自然地添加交互,相关组件有createjs、egret等。

3、使用H5 css3制作,这种方式也能像方式2一样制作出动画,但制作难度和复杂度都比方式2大,而且也有可能带来性能问题。

案例:

薛之谦XQQ动漫:

这支H5找来薛之谦拍视频,讲关于腾讯动漫作品的段子。视频内容十分有趣,成为了当时的现象级刷屏H5。

图片 38

图片 39

生命之下,想象之上——2015年腾讯互动娱乐发布会品牌H5:

这支H5使用createjs制作,以卡通动画的方式展示了男孩探寻想象力的旅程。动画分段播放,用户可以体会完每段动画的思想后,再点击继续播放下一段动画。

图片 40

图片 41

这是成年人不敢打开的童年:

这个H5动画属于一镜到底式的动画,用户一边滑动屏幕,动画一边顺序播放,而往相反方向滑动则会让动画倒序播放,滑动的速度会影响动画播放的速度,感觉就像是一边抽卷筒厕纸一边看到厕纸上画的逐格漫画一样。

图片 42

费玉清唱诛仙主题曲:

这个视频h5最大的特色是观看过程中长按按钮时会换了另一种魔性的画风,让用户不再只是被动地观看视频,而是通过操作发掘到更多有趣的内容,增强了用户的参与感和吸引力。

图片 43

图片 44

 

网站是如何变慢的?

全景交互

全景交互指将用户置于一个360度环绕的图片/视频环境下进行沉浸式的体验,用户可以通过转动手机或滑动屏幕来看这个环境里不同角度的内容并进行交互。如果将内容分成左右两个屏,带上VR眼镜,则可以进行VR体验。此玩法比较适合的场景有虚拟全景展示、身临其境的实景展示或活动现场展示。与此类似的还有商品的360度展示,用户拖动商品即可看到不同角度下商品的样子。

相关技术主要是3d旋转操作、陀螺仪方面的技术,全景图插件有造物节使用的css3d-engine ,全景视频组件有 Valiant360,还有一些收费组件如krpano。

案例:

小爷吴亦凡,凡心所向:

之前很火的吴亦凡打篮球全景视频H5,通过旋转手机看他风骚地带球,镜头对着他代表正在防守他,结束后游戏会算出玩家的防守有效率。

图片 45

图片 46

vivo-我们i音乐:

类似造物节的案例,用户在一个360度的虚拟空间里拖动画面和旋转手机看各角度下的样子,并可点击里面的物品进行互动。

图片 47

深圳欢乐谷实景地图:

在这个地图里,游客可以从高处欣赏欢乐谷的风景,切换各区域寻找游玩设施的位置,还可以发表评论留下足迹。该地图除了可以滑动、转动手机来浏览地图,还支持VR模式,将区域切换按钮移动到屏幕中间的锚点上一会儿便可切换游玩区域。

图片 48

图片 49

 

尺寸——慢的根源

兵无定势,水无常形,按照之前所说,我们选取了对我们最优的框架,做出来的网站应该很快,但第一轮需求结束后有第二轮,第二轮需求结束后有第三轮,网站版本会从1.1-X.1,业务的增长以及市场份额的角力带来的是一月一发布,一季一轮替,没有不变的道理。

框架最大的敌人是需求,代码最大的敌人是变更,最开始使用的是自己熟悉的技术,突然一天多出了一些莫名其妙的场景:

① webapp模式很不错,为了快速业务发展,将接入Hybrid技术,并且使用一套代码

② 微信入口已经很火了,为了快速业务发展,将接入微信入口,并且使用一套代码

③ UI组件已经旧了,换一批ios8风格的组件吧

④ 全站样式感觉跟不上潮流了,换一套吧

网站变慢的核心原因是尺寸的膨胀,尺寸优化才是前端优化的最重要命题,①、②场景是不可预知场景,面对这种不可预知场景,会写很多桥接的代码,而这类代码往往最后都会证明是不好的!

框架首次处理未知场景所做的代码,往往不是最优的,如Hybrid、如微信入口

1
框架首次处理未知场景所做的代码,往往不是最优的,如Hybrid、如微信入口

剩下两个场景是可预见的改变,但是此类变更会带来另一个令人头疼的问题,新老版本交替。业务20多个业务团队,不可能一个版本便全部改变,便有个逐步推进的过程。

全站样式替换/对未知场景的代码优化,很多时候为了做到透明,会产生冗余代码,为了做兼容,常常有很长一段时间新老代码共存的现象

1
全站样式替换/对未知场景的代码优化,很多时候为了做到透明,会产生冗余代码,为了做兼容,常常有很长一段时间新老代码共存的现象

于是不可预知造成的尺寸膨胀,经过重构优化,而为了做兼容,居然会造成尺寸进一步的增加

所谓优化不一定马上便有效果,开发人员是否扛得住这种压力,是否有全团队推动的能力会变得比本身技术能力更加重要

1
所谓优化不一定马上便有效果,开发人员是否扛得住这种压力,是否有全团队推动的能力会变得比本身技术能力更加重要

事实上的情况复杂的多,以上只是一厢情愿的以“接口统一”、“透明升级”为前提,但是透明的代价是要在重构代码中做兼容,而兼容又本身是需要重构掉的东西,当兼容产生的代码比优化还多的时候,我们可能就会放弃兼容,而提供一套接口完全不统一的东西;更加真实情况是我们根本不会去做这种对比,便直接将老接口废掉,这个时候造成的影响是“天怒人怨”,但是我们爽了,爽了的代价是单个团队的推动安抚。

这里请参考angularJS升级,新浪微博2.0接口与1.1不兼容问题,这里的微信接口提出,难保一年后不会完全推翻……

所以,尺寸变大的主要原因是因为冗余代码的产生,如何消除冗余代码是一个重点,也是一个难点。

多屏互动

多屏互动指在多个屏幕上体验活动,各自的操作会同时反应到其他屏幕上,一般以双屏互动为主。玩法有多人合作完成任务/互相竞技、你画我猜、一问一答、情侣互动小游戏、线下与现场观众互动、多个屏幕拼起来看视频等,也可以把手机屏幕当成控制器,用大屏幕来显示,例子有手机遥控器、谷歌的多人竞跑游戏。制作此类活动时,要注意兼顾好只有单人玩时的情况,此时可以将体验流程简单化,或者加上电脑一起参与。

相关技术主要是通过websocket或轮询接口进行同步通信和更新画面的内容。

案例:

CF手游&品客组队大战僵尸:

这个游戏是用手榴弹炸僵尸,既可单人玩,也可双人玩,双人玩的时候一人负责装薯片弹药,一人负责扔手榴弹。

图片 50

图片 51

奔驰抢车拔河:

2人拼手速点击屏幕,将奔驰拉到自己那边为赢。

图片 52

图片 53

爱9就在一起:

一个关于爱情的视频,需要使用2台手机才能看到完整的内容,视频会在2台手机上同步播放。和心爱的TA一起把手机拼起来看吧。我觉得这个h5在只有一个手机观看的时候,应该提供一个滑动屏幕或转动手机时可以看到另一半视频的功能。

图片 54

图片 55

微信线下多人飞机游戏:

微信无现金日的活动现场提供了一个可以让多人同时玩的飞机游戏。游客用手机操作飞机射击,当达到了一定分数就可换取礼物。在大屏幕上会显示多人一起玩的游戏画面。这种方式增强了主办方与游客、游客之间的互动,也减少了游客的排队时间。飞机游戏的设计,也让人想起了第一款微信游戏就是飞机游戏,有一种回归初心的感觉。

图片 56

谷歌多人在线运动小游戏:

总共有三个竞赛小游戏,分别是跑步、骑单车和游泳,最多能同时让四个人加入游戏。玩家在手机上按照操作指示去玩,电脑上则同时显示游戏画面。当只有单人玩的时候,会有电脑参与游戏。

图片 57

图片 58

 

版本轮替——哪些能删的痛点

数月后,20多个团队悉数切入到最新的框架,另一个令人头疼的问题马上又出来了,虽然大家样式都接入到最新的风格了,但是老的样式哪些能删?哪些不能删又是一个令人头疼的问题。

几个月前维护CSS同事嫌工资低了,换了一个同事维护全站基础css;再过了一段时间,组织架构调整,又换了一个同事维护;再过了一段时间,正在维护css的同事觉得自己级别低了,在公司内部等待晋级确实熬不住,于是也走了。这个基础css俨然变成了一笔烂账,谁也不敢删,谁也不愿意动,动一下错一下。

这个问题表面上看是一个css问题,其实这是一个前端难题,也是过度解耦,拆分机制不正确带来的麻烦。

CSS是前端不可分割的一部分,HTML模板与Javascript可以用requireJS处理,很大程度上解决了javascript变量污染的问题,css一般被一起分离了出来,单独存放。一个main.css包含全站重置的样式,表单、列表、按钮的基础样式,完了就是全站基础的UI组件。

总有业务团队在实际做项目时会不自主的使用main.css中的一些功能,如果只是使用了基础的重置还好,但是一旦真的使用其中通用的表单、列表等便2B了

main.css的初衷当然是将各个业务团队通用的部分提炼出来,事实上也该这样做,但理想很丰满,现实很残酷,不同的人对SEO、对语义化对命名的理解不太一样,换一个人就会换一套东西。第一批项目上线后,过了几个月,开发人员成长非常巨大,对原来的命名结构,完全不削一顾,自己倒腾出一套新的东西,让各个团队换上去,其它团队面对这种要求是及其头疼的,因为各个团队会有自己的CSS团队,这样一搞势必该业务团队的HTML结构与CSS要被翻新一次,这样的意义是什么,便不太明显了。2个星期过去了,新一批“规范化”的结构终于上线了,2个月后所有的业务团队全部接了新的结构,似乎皆大欢喜,但是那个同事被另一个团公司挖过去当前端leader了,于是一大群草泥马正在向业务团队的菊花奔腾过去!这里的建议是:

业务团队不要依赖于框架的任何dom结构与css样式,特别不要将UI组件中的dom结构与样式单独抠出来使用,否则就准备肥皂吧

1
业务团队不要依赖于框架的任何dom结构与css样式,特别不要将UI组件中的dom结构与样式单独抠出来使用,否则就准备肥皂吧

本文由千赢国际官网发布于千赢网页手机版登入,转载请注明出处:浅谈移动前端的最佳实践,玩法知多少

关键词: 千赢国际官网