开会大体讲解、讨论与排期 -> 交互设计 -> 视觉设计 -> 页面页面制作 -> 前端开发 -> 测试
每个步骤环环相扣,每个职位都需要和其前后的人沟通协调。
测试遇到问题则会反馈到相应环节负责人。
当然,涉及的职位也不仅于此,还有法务同事审核内容是否符合当前法规等等。
前端开发离不开构建工具,除了敲代码,其余都交给构建工具(如组件开发、CSS 兼容处理、图片 Base64、图片雪碧图和压缩处理等)。
在 Athena 中,文件层级结构如下:项目 project -> 模块 module(具体每个活动) -> 页面 page -> 部件 widget。
举例: 某项目 -> X、Y 活动 -> 预热页和高潮页 -> 头部、弹框等 widget。一般文件目录如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
Xproject
- gb (公共部分,如初始化样式和一些常用 widget)
- X活动
- page
- 预热页
- 高潮页
- widget
- header
- footer
- diglog
- Y 活动
- ...
|
刚开始接触时,存在这样的一个疑惑:什么是 widget,一个不可复用的页面头部可以作为 widget 吗?
答:我最初的想法是:“错误地把 widget 当成 component,component 一直被强调的 特性之一是可复用性。对于不可复用的部分就不应该抽出为一个widget了?”其实对于一个相对独立的功能,我们就可把它抽出来。这无疑会增强程序的可维护性。
对于一个项目,一般一个模块由一个人负责。但考虑到每个模块间可能存在(或未来存在)可复用的 widget,需要规范命名以形成命名空间,防止冲突(具体会在下面的规范-命名中阐述)。
Component 与 Widget 的区别
Component 是更加广义抽象的概念,而Widget是更加具体现实的概念。所以Component的范围要比Widget大得多,通常 Component 是由多个 Widget 组成。
举个例子,可能不是很恰当,希望帮助你的理解,比如家是由床,柜子等多个 Component 组成,柜子是由多个抽屉 Widget 组成的。
而 Component 和 Widget 的目的都是为了模块化开发。
其实,在这里并没有对 widget 和 component 做这么细的区分。
正如上面讨论的,一个页面由多个 widget 组成。因此,一个页面看起来如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<body ontouchstart>
<div class="wrapper">
<%= widget.load("app_market_main_header") %>
<%= widget.load("app_market_answer") %>
<%= widget.load("app_market_coupons") %>
<%= widget.load("app_market_camp") %>
<%= widget.load("app_market_collocation") %>
<%= widget.load("app_market_dialog") %>
div>
|
widget 一般存在可复用性。但如何控制细粒度呢?分得越细代码就越简洁,但工作量和维护难度可能会上升,因此需要权衡你当时的情况。
由于一个项目中,一个模块由某一个人负责,但模块之间的 widget 存在或未来存在可复用的可能(而且开发可能会为你的页面添加已有的组件,如页面会嵌在某 APP 内,该 APP 已有现成的一些提示框)。因此,需要命名空间将其它们进行区分以防止冲突。由于 CSS 不存在命名空间,因此只能通过类似 BEM 的方式(具体根据团队的规范),如:app_market_header
、app_market_list_item
。app_market
是模块(即某个活动)的标识,在该项目下,它是唯一的。
另外,还有一点:类名是否要按照 html 层级关系层层添加呢?如:
1
2
3
|
div .app_market_header
div .app_market_header_icon
div.app_market_header_**
|
对于 app_market_header_icon
,尽管在 header 中,但 icon 并不只属于 header,而属于整个模块(活动),那么我们就可以改为 app_market_icon
。
老司机 Code review 后,讲了以下内容:
反面教材:
1
2
3
4
5
6
7
8
9
|
< div class= "app_market_answer">
< div class= "app_market_secheader"> div>
< div class= "app_market_answer_list">
< div class= "app_market_answer_item">
< div class= "app_market_answer_item_top"> div>
< div class= "app_market_answer_item_middle"> div>
div>
div>
|
存在的问题是:嵌套层级越深,类名就越长。
较好的解决方案:
1
2
3
4
5
6
7
8
9
|
< div class= "app_market_answer">
< div class= "app_market_secheader"> div>
< div class= "app_market_answer_list">
< div class= "app_market_answer_item">
< div class= "app_market_answer_itop"> div>***
< div class= "app_market_answer_imid"> div>***
div>
div>
|
这是基于『姓名』原理进行优化的,举例:app_market_answer_item
是姓名(库日天),那么它的子元素只需继承它的『姓』(库姆斯) app_market_answer_itop
,而不是它的姓名(库日天姆斯) app_market_answer_item_top
。每当类名达到三到四个单词长时,就要考虑简化名字。
进一步优化,app_market 可以看成是『复姓』,有时为了书写便利,可以以两个单词的首字母结合形成一个新的『新姓』- 『am』。当然,追求便利的副作用是牺牲了代码的可读性。如果你负责的项目或页面没有太大的二次维护或者交叉维护的可能性,推荐做此简化。
BTW:此简化后的『姓』可以在代码中稍加注释说明,如下代码所示:
1
2
3
4
5
6
7
8
9
10
|
<div class="am_answer">
<div class="am_secheader"> div>
<div class="am_answer_list">
<div class="am_answer_item">
<div class="am_answer_itop"> div>
<div class="am_answer_imid"> div>
<a href="javascript:;" class="am_answer_ibtm">去围观 a>
div>
div>
|
1
2
3
|
<div>
<a href="javascript:;">... a>
div>
|
至少加一个类名,任何时候都尽量要『针对类名书写样式,而不是针对元素书写样式』,除非你能预判元素是末级元素。
因此对于以下 CSS:
1
2
3
|
.app_market_coupons > div {
...
}
|
可优化成:
1
2
3
|
.app_market_coupons > .xxx {
...
}
|
移动端采用 rem 布局方式。通过动态修改 html 的 font-size 实现自适应。
REM 布局有两种实现方式:CSS 媒介查询和 JavaScript 动态修改。由于 JavaScript 更为灵活,因此现在更多地采用此方式。
凹凸的实现方式是:在 head
标签末加入以下代码
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
|
<script type="text/javascript">
! function(){
var maxWidth= 750;
document.write( '');
var o2_resize= function(){
var cw,ch;
if( document&& document.documentElement){
cw= document.documentElement.clientWidth,ch= document.documentElement.clientHeight;
}
if(!cw||!ch){
if( window.localStorage[ "o2-cw"]&& window.localStorage[ "o2-ch"]){
cw= parseInt( window.localStorage[ "o2-cw"]),ch= parseInt( window.localStorage[ "o2-ch"]);
} else{
chk_cw(); //定时检查
return ; //出错了
}
}
var zoom=maxWidth&&maxWidth
window.localStorage[ "o2-cw"]=cw, window.localStorage[ "o2-ch"]=ch;
//zoom=Math.min(zoom,zoomY);//保证ip6 wechat的显示比率
window.zoom= window.o2Zoom=zoom;
document.getElementById( "o2HtmlFontSize").innerHTML= 'html{font-size:'+(zoom* 20)+ 'px;}.o2-zoom,.zoom{zoom:'+(zoom/ 2)+ ';}.o2-scale{-webkit-transform: scale('+zoom/ 2+ '); transform: scale('+zoom/ 2+ ');} .sq_sns_pic_item,.sq_sns_picmod_erea_img{-webkit-transform-origin: 0 0;transform-origin: 0 0;-webkit-transform: scale('+zoom/ 2+ ');transform: scale('+zoom/ 2+ ');}';
},
siv,
chk_cw= function(){
if(siv) return ; //已经存在
siv=setInterval( function(){
//定时检查
document&& document.documentElement&& document.documentElement.clientWidth&& document.documentElement.clientHeight&&(o2_resize(),clearInterval(siv),siv= undefined);
}, 100);
};
o2_resize(); //立即初始化
window.addEventListener( "resize",o2_resize);
}();
script>
|
从以上代码可得出以下信息:
zoom
为 1