我的第一次移動端頁面製作 — 總結與思考

NO IMAGE

最近被分配到移動端開發組,支援某活動的頁面頁面製作。這算是我第一次真正接觸移動端頁面製作,下面就談談個人總結和思考。

整體流程

開會大體講解、討論與排期 -> 互動設計 -> 視覺設計 -> 頁面頁面製作 -> 前端開發 -> 測試

每個步驟環環相扣,每個職位都需要和其前後的人溝通協調。

測試遇到問題則會反饋到相應環節負責人。

當然,涉及的職位也不僅於此,還有法務同事稽核內容是否符合當前法規等等。

構建工具

Athena

前端開發離不開構建工具,除了敲程式碼,其餘都交給構建工具(如元件開發、CSS 相容處理、圖片 Base64、圖片雪碧圖和壓縮處理等)。
Athena 中,檔案層級結構如下:專案 project -> 模組 module(具體每個活動) -> 頁面 page -> 部件 widget。

舉例: 某專案 -> X、Y 活動 -> 預熱頁和高潮頁 -> 頭部、彈框等 widget。一般檔案目錄如下:

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

正如上面討論的,一個頁面由多個 widget 組成。因此,一個頁面看起來如下:

<body ontouchstart>
<div class="wrapper">
<!-- S 主會場頭部 -->
<%= widget.load("app_market_main_header") %>
<!-- E 主會場頭部 -->
<!-- S 達人問答區 -->
<%= widget.load("app_market_answer") %>
<!-- E 達人問答區 -->
<!-- S 優惠券 -->
<%= widget.load("app_market_coupons") %>
<!-- E 優惠券 -->
<!-- S 達人集中營 -->
<%= widget.load("app_market_camp") %>
<!-- E 達人集中營 -->
<!-- S 達人穿搭公式 -->
<%= widget.load("app_market_collocation") %>
<!-- E 達人穿搭公式 -->
<!-- S 卡券相關彈框 -->
<%= widget.load("app_market_dialog") %>
<!-- E 卡券相關彈框 -->
</div>

widget 一般存在可複用性。但如何控制細粒度呢?分得越細程式碼就越簡潔,但工作量和維護難度可能會上升,因此需要權衡你當時的情況。

CSS 命名

名稱空間

由於一個專案中,一個模組由某一個人負責,但模組之間的 widget 存在或未來存在可複用的可能(而且開發可能會為你的頁面新增已有的元件,如頁面會嵌在某 APP 內,該 APP 已有現成的一些提示框)。因此,需要名稱空間將其它們進行區分以防止衝突。由於 CSS 不存在名稱空間,因此只能通過類似 BEM 的方式(具體根據團隊的規範),如:app_market_headerapp_market_list_itemapp_market 是模組(即某個活動)的標識,在該專案下,它是唯一的。

另外,還有一點:類名是否要按照 html 層級關係層層新增呢?如:

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 後,講了以下內容:
反面教材:

<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>
<a href="javascript:;" class="app_market_answer_item_bottom">去圍觀</a>
</div>
</div>

存在的問題是:巢狀層級越深,類名就越長。

較好的解決方案:

<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>***
<a href="javascript:;" class="app_market_answer_ibtm">去圍觀</a>***
</div>
</div>

這是基於『姓名』原理進行優化的,舉例:app_market_answer_item 是姓名(庫日天),那麼它的子元素只需繼承它的『姓』(庫姆斯) app_market_answer_itop,而不是它的姓名(庫日天姆斯) app_market_answer_item_top。每當類名達到三到四個單詞長時,就要考慮簡化名字。

進一步優化,app_market 可以看成是『複姓』,有時為了書寫便利,可以以兩個單詞的首字母結合形成一個新的『新姓』- 『am』。當然,追求便利的副作用是犧牲了程式碼的可讀性。如果你負責的專案或頁面沒有太大的二次維護或者交叉維護的可能性,推薦做此簡化。

BTW:此簡化後的『姓』可以在程式碼中稍加註釋說明,如下程式碼所示:

<!-- am = app_market -->
<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>

針對類名書寫樣式

<div>
<a href="javascript:;">...</a>
</div>

至少加一個類名,任何時候都儘量要『針對類名書寫樣式,而不是針對元素書寫樣式』,除非你能預判元素是末級元素。
因此對於以下 CSS:

.app_market_coupons > div {
...
}

可優化成:

.app_market_coupons > .xxx {
...
}

技術涉及

REM

移動端採用 rem 佈局方式。通過動態修改 html 的 font-size 實現自適應。

實現方式

REM 佈局有兩種實現方式:CSS 媒介查詢和 JavaScript 動態修改。由於 JavaScript 更為靈活,因此現在更多地採用此方式。

JavaScript

凹凸的實現方式是:在 head 標籤末加入以下程式碼

<script type="text/javascript">
!function(){
var maxWidth=750;
document.write('<style id="o2HtmlFontSize"></style>');
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<cw?maxWidth/375:cw/375,zoomY=ch/603;//由ip6 weChat
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>

從以上程式碼可得出以下資訊:

  1. 以 iPhone 6 為基準,iPhone 6 的縮放比 zoom1

  2. 由於只針對移動端,因此最大寬度為768(恰好等於 iPad 的豎屏寬度)

  3. 通過 document.documentElement.clientWidth 獲取視口寬度

  4. resize 事件主要考慮橫豎屏切換和你在PC上除錯時?

  5. zoom 係數是 20。係數決定了在寬度 375 的 iPhone6 下,1 rem 的值是多少 px(20px)。當然如果想過渡到 vw,可以將 zoom 係數設定為 3.75,那麼 100rem 就是 375px 了

為什麼要用

有人說 rem 佈局是 vwvh 的替換方案,當 vwvh 成熟時,兩者可能會各司其職吧。

vw 的相容性:在安卓 4.3 及以下是不支援的。

哪些地方要用

由於 rem 佈局是相對於視口寬度,因此任何需要根據螢幕大小進行變化的元素(width、height、position 等)都可以用 rem 單位。

但 rem 也有它的缺點——不精細(在下一節闡述),其實這涉及到了瀏覽器渲染引擎的處理。因此,對於需要精細處理的地方(如通過 CSS 實現的 icon),可以用 px 等絕對單位,然後再通過 transform: scale() 方法等比縮放。

字型

font-size 是否也要用 rem 單位呢? 這也是我曾經糾結的地方。如果不等比縮放,對不起設計師,而且對於小螢幕,一些元素內的字型會換行或溢位。當然這可以通過 CSS3 媒介查詢解決這種狀況。

字型不採用 rem 的好處是:在大屏手機下,能顯示更多字型。

看到 網易新聞聚划算 的字型大小都採用 rem 單位,我就不糾結了。當然,也有其它網站是採用絕對單位的,兩者沒有絕對的對與錯,取決於你的實際情況。

缺點

小數點(不精細,有間隙)

由於 rem 佈局是基於某一裝置實現的(目前一般採用 iPhone6),對於 375 倍數寬的裝置無疑會擁有最佳的顯示效果。而對於非 375 倍數寬的裝置,zoom 就可能是擁有除不盡的小數,根元素的字型大小也相應會有小數。而瀏覽器對小數的處理方式不一致,導致該居中的地方沒完全居中,但你又不能為此設定特定樣式(如 margin-top: *px;),因為瀏覽器多如牛毛,這個瀏覽器微調居中了,而原本居中的瀏覽器變得不居中了。

對於圖示 icon,rem 的不精細導致通過多個元素(偽元素)組合而成的 icon 會形成錯位/偏差。因此,在這種情況下,需要權衡是否需要使用 CSS 實現了。

SASS

SASS 無疑增強了原本宣告式的 CSS,為 CSS 注入了可程式設計等能力。在這次專案,算是我第一次使用 SASS,由於構建工具和基礎庫的完善,只需通過檢視/模仿已有專案的 SASS 用法,就能快速上手。後續還是要系統地學習,以更合理地使用 SASS。

使用 SASS 的最大問題是:層級巢狀過深,這也是對 SASS 理解不深入的原因。可以關注一下轉譯後的 CSS。

相容性

這次專案的 APP 採用手機自帶瀏覽器核心,而這些瀏覽器核心依賴於系統版本等因素。另外,國產機也會對這些核心進行定製和修改。特別是華為、OPPO。

下面列出我所遇到的相容性問題(不列具體機型,因為這些相容性處理終會過時,不必死記硬背,遇到了能解決就好(要求基礎紮實)):

  • flexbox:在構建工具處理下(實現了新舊語法)可以大膽用,但個別裝置不支援 flex-wrap: wrap。因此對於想使用 flex-wrap 實現自動分行的情況,建議使用其他實現。如果個數固定(如 N 行,每行 M 個),則可使用 N 個 flexbox(這樣就可以使用 flexbox 的特性了)。flexbox 的其他屬性也有支援不好的情況,可以通過顯式宣告 display、overflow、width、height 等方法解決。

  • background-size:需要單獨寫,否則在 安卓 4.3 及以下,IOS 6.1及以下不相容

  • 漸變:線性漸變大膽使用,徑向漸變有相容性問題。但是不建議對整體背景使用,會有效能問題(可簡單地通過 1px 高的圖片替代,注意,不要 background-size: 100% auto; 應該採用 background-size: 100% 1px; 因為有些瀏覽器(視口寬度較小)會忽略小數點【auto = img.Height * (screen.Width/img.Width)】,導致圖片未顯示)。另外,需要注意的是:透明的色標在iOS 預設是黑色的,即 transparent 等於 rgba(0,0,0,0)。因此即使是完全透明的色標,也要指定顏色。否則後果如下:
    此處輸入圖片的描述

  • classlist.remove(String[, String]),傳遞多個引數時,會有不相容的情況。建議每次寫一個。add (String[, String])同理。

  • 根節點 html font-size 渲染錯誤:在華為、魅族的某裝置上(手Q),會出現一個非常奇葩的渲染 Bug,同一個網頁,“掃一掃”開啟 html 的 font-size 正常,直接點選連結會出現渲染出來的 html font-size 會比設定得值大(如:設定25.8,渲染出來是 29),因此導致整體變大,且佈局錯亂。
    我的方法是:為 html font-size 重新設定大小:渲染字型大小 – (渲染與正常差值)

    function getStyle(ele, style) {
    return document.defaultView.getComputedStyle(ele, null)[style]
    }
    ;(function fixFontSize() {
    var target = window.o2Zoom * 20
    var cur = parseInt(getStyle(document.documentElement, "fontSize"))
    while(cur - target >= 1) {
    document.documentElement.style["fontSize"] = target - (cur - target)   "px"
    cur = parseInt(getStyle(document.documentElement, "fontSize"))
    }          
    })();

有網友提供這個方法 <meta name="wap-font-scale" content="no">,經測試不可行。此方法是針對 UC 瀏覽器的。

上面主要列出了對使用有影響的相容性問題,有些由於瀏覽器渲染引擎導致的問題(不影響使用),若無法通過 transform、z-index 等解決,也許只能通過 JavaScript 解決或進行取捨了。

其他一些知識點

  • 圖片佔位元素:對於寬高比例固定的坑位(如商品列表項),通過將圖片放置在佔位元素中,可避免圖片載入時引起的頁面抖動和圖片尺寸不一致而導致的頁面佈局錯亂。程式碼實現:

    .img_placeholder {
    position: relative;
    height: 0;
    overflow: hidden;
    padding-top: placeholder 的高/寬%; // padding-top/bottom: 百分比; 是基於父元素的寬度
    img {
    width: 100%;
    height: auto;
    position: absolute;
    left: 0;
    top: 0;
    }
    }
  • 1px:在 retina 螢幕下,1 CSS畫素是用 4 個物理畫素表示,為了在該螢幕下顯示更精細,通過為 ::after 應用以下程式碼(以上邊框為例):

    div {
    position: relative;
    &::after {
    content: '';
    position: absolute;
    z-index: 1;
    pointer-events: none;
    background: $borderColor;
    height: 1px;left: 0;right: 0;top: 0;
    @media only screen and (-webkit-min-device-pixel-ratio:2) {
    &{
    -webkit-transform: scaleY(0.5);
    -webkit-transform-origin: 50% 0%;
    }
    }
    }
    }
  • 根據元素個數應用特定樣式:

    /* one item */
    li:first-child:nth-last-child(1) {
    width: 100%;
    }
    /* two items */
    li:first-child:nth-last-child(2),
    li:first-child:nth-last-child(2) ~ li {
    width: 50%;
    }
    /* three items */
    li:first-child:nth-last-child(3),
    li:first-child:nth-last-child(3) ~ li {
    width: 33.3333%;
    }
    /* four items */
    li:first-child:nth-last-child(4),
    li:first-child:nth-last-child(4) ~ li {
    width: 25%;
    }

    應用樣例有:根據元素個數自適應標籤樣式。

根據元素個數自適應標籤樣式
而對於反方向標籤,可先首先對整體 transform: scale(-1),然後再對字型 transform: scale(-1) 恢復從左向右的方向。效果如下:
標籤反向

  • 卡券:『帶孔且背景是漸變的卡券』在複雜背景中的實現。由於背景是複雜的(非純色),因此孔不能簡單地通過覆蓋(與背景同色)產生。這裡可以應用徑向漸變 background-image: radial-gradient(rem(189/2) 100%, circle, transparent 0, transparent 3px, #fa2c66 3px);,其中 3px 是孔的半徑。另外,卡券的上下部分是線性漸變的,因此可以在上下部分分別通過偽類元素新增 background-image: linear-gradient(to top, #fa2e67 0, #fb5584 100%);,當然,要從離外上/下邊界 3px 的地方開始。雖然這不能完美地從最邊界開始,但效果還是可以的。但由於徑向漸變的相容性問題,我最終還是用圖片替換了這種實現。?
    帶孔且背景是漸變的卡券

  • 多行文字的多行padding:讓背景只出現在有文字的地方,可直接設定 display: inline;,但還會存在一個問題是:padding 只會出現在多行文字的首和尾,對於需要為每行文字的首尾都需要相同的 padding,可以參考這篇文章:《multi-line-padded-text》 。該文章提供了多種實現方式,根據具體情況選擇一種即可。另外,對於每行的間距,可通過設定 line-height 和 padding-top/bottom 實現,其中 line-height 要大於(字型高度 padding-top/bottom)。
    此處輸入圖片的描述

此處輸入圖片的描述

  • 最小字型限制:PC上最小字型是 12px、移動端最小是 8px,當然可通過 transform:scale() 突破限制。

不止頁面頁面製作

  1. 基礎:合理運用 CSS 的威力更好地完成對設計稿的重現目的。

  2. 溝通:由於分工較細,只負責頁面製作的同學,需要與產品和設計溝通,以達到交給開發後更少修改的目的。如哪些地方可跳轉、哪些地方最多顯示幾行文字、超出如何處理(直接隱藏/省略號等)、坑位中的圖片擺放(頂部對齊/居中等)等等。

  3. 程式碼上的溝通:HTML 註釋要寫好、HTML 與 CSS 程式碼要規範(命名等)清晰。

思考

由於工具的成熟,我不需要考慮構建工具的搭建。
由於釋出方式的成熟,頁面製作和開發能更好地分離,頁面製作者負責輸出 HTML、CSS,開發負責 copy html 程式碼和引入 CSS 頁面片。CSS 頁面片由頁面製作者更新發布,開發無需關心。這達到了互不干擾、多執行緒並行的效果。
成熟的基礎設施讓我們免除了非程式碼相關的煩惱,但這也讓我擔心:假如有一天我脫離了這些基礎設施,我該如何保持高效。

延伸:頁面片是什麼?

CSS 頁面片

<!-- #include virtual="/folder/branch.shtml" -->
<link combofile="/folder/branch.shtml" rel="stylesheet" href="//website/folder/gb.min_1151b5b0.css,/folder/branch.min_925332fc.css" />

JS 頁面片

<!-- #include virtual="/folder/branch_js.shtml" -->
<script combofile="/folder/branch.shtml" src="//website/path/branch.min_8971778a.js"></script>

Combo Handler是Yahoo!開發的一個Apache模組,它實現了開發人員簡單方便地通過URL來合併JavaScript和CSS檔案,從而大大減少檔案請求數。 http://www.cnblogs.com/zhengy…


這就是我的第一次…? 學習很多,完!

以上僅是我個人完成某專案頁面製作的思考和總結,不小心暴露了團隊下限。?