優秀網站看前端:小米Note介紹頁面代碼分析
剛開始經營博客的時候,我寫過不少“扒皮”系列的文章,主要介紹一些知名站點上有趣的交互效果,然后試著實現它們。后來開始把注意力挪到一些新穎的前端技術上,“扒皮”系列便因此封筆多時。今天打算重開“扒皮”的坑,不過咱掛個優雅點的名字——“優秀網站看前端”,顧名思義的,也是尋覓一些值得玩味的趣味網站,來學習它們的前端技術和交互理念。
作為本系列的開篇,我們拿“買手機送國土”的小米來打頭陣,緣由是鄙人有著更換手機的打算又剛好看上小米note高配版,于是逛了下小米note的介紹頁面,感覺交互做的挺不錯,特別是CSS3動畫部分,不妨就直接寫篇文章和大家一同來學習和分享。
小米note的介紹頁面地址如下,大家可以先去領略它的交互效果:
字體
該網站首先吸引我的是其在標題頭等地方使用的字體,由于本身也喜歡搞設計,所以一眼就注意到這兩行細體字絕非宋體黑體和雅黑,check了一下,字體名為FZLTXHK(也就是方正蘭亭纖黑體):
會不會有點小詫異,常規我們是很不推薦在網頁上通過font-face來引入第三方中文字體的,畢竟一個完整的中文字體包常規都是好幾M的大小,一來得讓客戶端花時間請求、浪費用戶流量,二來會造成頁面字體效果切換的閃動(FOUT——flash of unstyled text)現象。
小米作為一個注重用戶體驗的公司,相信也不會做這么不合常理的事情(喂,沒打算聊國土啊喂)。那么字體這塊,小米自然也是動了些手腳——只封裝了頁面上需要使用到的文字。不信?你可以試著把用到第三方字體的標題內容更改為其它內容,你會發現很多文字是不被該字體所支持的:
那么小米的射雞獅真是辛苦,每次修改頁面都要手動打包新的字體,真是兢兢業業可歌可泣。。。。其實未然,畢竟現在不是刀耕火種的原始社會,我們可以直接請node包加持~
于是 “字蛛(FontSpider)” 這款工具可以粉墨登場啦(別亂用成語啊親~)—— 字蛛通過分析本地 CSS 與 HTML 文件獲取 WebFont 中沒有使用的字符,并將這些字符數據從字體中刪除以實現壓縮,同時生成跨瀏覽器使用的格式。
字蛛的使用方式在其官網上已經解釋的很清楚了,本文不贅述,但先聊聊@font-face的匹配格式,也就是聊一聊WEB上常用的字體格式。
@font-face中引入的字體文件可以通過format方法來幫助瀏覽器匹配到對應的字體格式,常規各瀏覽器所支持的字體格式有如下幾種:
一、TureTpe(.ttf)格式:
.ttf字體是Windows和Mac的最常見的字體,是一種RAW格式,因此他不為網站優化,支持這種字體的瀏覽器有(IE9+,Firefox3.5+,Chrome4+,Safari3+,Opera10+,iOS Mobile Safari4.2+);
二、OpenType(.otf)格式:
.otf字體被認為是一種原始的字體格式,其內置在TureType的基礎上,所以也提供了更多的功能,支持這種字體的瀏覽器有(Firefox3.5+,Chrome4.0+,Safari3.1+,Opera10.0+,iOS Mobile Safari4.2+);
三、Web Open Font Format(.woff)格式:
.woff字體是Web字體中***格式,他是一個開放的TrueType/OpenType的壓縮版本,同時也支持元數據包的分離,支持這種字體的瀏覽器有(IE9+,Firefox3.5+,Chrome6+,Safari3.6+,Opera11.1+);
四、Embedded Open Type(.eot)格式:
.eot字體是IE專用字體,可以從TrueType創建此格式字體,支持這種字體的瀏覽器有(IE4+);
五、SVG(.svg)格式:
.svg字體是基于SVG字體渲染的一種格式,支持這種字體的瀏覽器有(Chrome4+,Safari3.1+,Opera10.0+,iOS Mobile Safari3.2+)。
那么綜上所述,一個@font-face只要匹配了.eot 和 其它某種字體(***是.woff),就基本能在大多數瀏覽器上正常顯示了。不過查看小米的樣式(點我查看),它只匹配了.woff。該字體文件采用了base64編碼形式,這樣有個好處——減少了一次文件請求,也能有效防止上文提過的FOUT現象。
不過這個base64怎么折騰出來的?或許小米用字蛛之類的工具獲得字體壓縮文件后,再通過某種方式(比如在這里轉換)將其轉為base64編碼形式即可。
另外小米還使用了一個CSS3樣式:
-webkit-font-smoothing:antialiased
該屬性可以使頁面上的字體抗鋸齒,使用后字體看起來會更清晰舒服(特別適用于字號較小的文本內容)。
有三種可選值:
none | subpixel-antialiased | antialiased
它們的區別見下圖:
transition動畫
小米的頁面到處都充滿了視覺差滾動效果,有種隨時給你小驚喜的感覺:
如上圖的動畫,是由transition實現的,大致步驟如下:
⑴ 給所有要動畫的元素設置transition屬性,比如 transition:transform 1s ;
⑵ 給所有動畫元素添加一個class="visible" ,該class中定義了動畫的最終狀態;
⑶ 頁面DOMReady的時候遍歷所有動畫元素($(".visible")),檢查它們是否還沒被滾動條滾上來,如果還在窗口可視區域下方,則移除它們"visible"的class。該步驟主要是用于處理用戶下拉頁面到某個位置然后刷新頁面,這時要求窗口可視區域及其上方的元素都應跳過動畫的狀態,直接到達動畫最終狀態;
⑷ 監聽onscroll事件,移動到某個動畫元素的位置時,則移除該元素名為"visible"的class。
我們可以粗略地寫個場景和腳本來實現:
- <!DOCTYPE html>
- <html>
- <head lang="en">
- <meta charset="UTF-8">
- <title>動畫模擬</title>
- <script src="jq.js"></script>
- <style>
- article,div{margin: 380px 0;border: solid 1px gray;}
- article > section{width:50px;height:50px;background:red;transform: translate3d(-100px, -60px, 0);opacity: 0;transition: all .8s;}
- article > section.visible {transform: translate3d(0, 0, 0);opacity: 1;}
- div > span{background:blue;transform: scale(.2);opacity: 0;transition: all 2s;}
- div > span.visible {transform: scale(1);opacity: 1;}
- div > p {width:50px;height:50px;background:yellow;transform: translate3d(90px, 100px, 0);opacity: 0;transition: all 1.5s;}
- div > p.visible {transform: translate3d(0, 0, 0);opacity: 1;}
- </style>
- <script>
- $(function(){
- var elmArr = [],
- $win = $(window);
- $(".visible").each(function(i,elm){
- $(elm).data("ot",$(elm).offset().top);
- elmArr.push(elm);
- });
- dealClass(1);
- $win.on("scroll",dealClass);
- function dealClass(isRemove){
- var top = $win.height() + $win.scrollTop();
- if(isRemove!=1) { //滾動頁面時的判斷,并添加class="visible"
- for (var i = 0,$elem; i < elmArr.length; i++) {
- $elem = $(elmArr[i]);
- if ($elem.data("ot") <= top) {
- $elem.addClass("visible");
- elmArr.splice(i, 1);
- --i;
- }
- }
- }else{ //初始化頁面時的判斷,并刪除class="visible"
- for (var i = 0,$elem; i < elmArr.length; i++) {
- $elem = $(elmArr[i]);
- if ($elem.data("ot") >= top) {
- $elem.removeClass("visible");
- }
- }
- }
- }
- })
- </script>
- </head>
- <body>
- <article>
- <section class="visible"></section>
- </article>
- <div>
- <span class="visible">hello</span>
- </div>
- <div>
- <p class="visible"></p>
- </div>
- </body>
- </html>
效果如下:
transition動畫效果默認是線性展示的(linear),我們通過設置其timing-function屬性可以讓效果變得更靈活,比如這個效果:
此處的transition-timing-function屬性被設置為 cubic-bezier(.15, .73, .37, 1.2) ,表示按照該貝塞爾曲線函數來執行動畫(了解更多請戳我)。
你可以試著把我們上方例子中的 transition:XXX 修改為:
transition: transform 1.5s cubic-bezier(.15, .73, .37, 1.2),opacity 1s;
然后查看其效果:
如果在transition的***加上一個時間單位,可以延遲觸發動畫效果。比如上面五個手機(5個<li>標簽)是按順序依次出來的,那么我們可以給第2個<li>設定0.2秒的延遲,給第3個<li>設定0.4秒的延遲,給第4個<li>設定0.6秒的延遲。。。
我們拿第2個<li>的transition來示例:
transition:transform 1s cubic-bezier(.15, .73, .37, 1.2) .2s;
由于在末位寫上了0.2s的transition-delay值,故第二個手機會相較***個手機完0.2秒執行動畫。
#p#
animate動畫
animate動畫很適用于那些需要分段展示不同動畫的場景,比如(該效果頁面地址):
該卡槽是由一個div(卡槽本身)內嵌一個span(***淡入顯示的鏤空處文本)組成的,div觸發動畫時(跟transition一樣加個觸發class)直接從下往上顯示(2s),而span雖然是同時被觸發動畫,但它延遲2s才執行,所以營造了“在div動畫結束后,span才開始觸發動畫”的視覺效果。
我們照樣拿前面的例子來修改:
- <!DOCTYPE html>
- <html>
- <head lang="en">
- <meta charset="UTF-8">
- <title>動畫模擬</title>
- <script src="jq.js"></script>
- <style>
- body,html{height: 100%;}
- @-webkit-keyframes ani-fade-in {
- 0% {
- opacity: 0
- }
- 100% {
- opacity: 1
- }
- }
- @keyframes ani-fade-in {
- 0% {
- opacity: 0
- }
- 100% {
- opacity: 1
- }
- }
- @-webkit-keyframes ani-fade-in-up {
- 0% {
- -webkit-transform: translateY(100px);
- opacity: 0
- }
- 100% {
- -webkit-transform: translateY(0);
- opacity: 1
- }
- }
- @keyframes ani-fade-in-up {
- 0% {
- -webkit-transform: translateY(100px);
- opacity: 0
- }
- 100% {
- -webkit-transform: translateY(0);
- opacity: 1
- }
- }
- article{margin: 500px 0;}
- div{width:50px;height:50px;background:green;opacity: 0;}
- div.visible{-webkit-animation:ani-fade-in-up 2s ease forwards;animation:ani-fade-in-up 2s ease forwards;}
- div > span{background:blue;opacity: 0;}
- div.visible > span {-webkit-animation:ani-fade-in 1s 2s ease forwards;animation:ani-fade-in 1s 2s ease forwards;}
- </style>
- <script>
- $(function(){
- var elmArr = [],
- $win = $(window);
- $(".visible").each(function(i,elm){
- $(elm).data("ot",$(elm).offset().top);
- elmArr.push(elm);
- });
- dealClass(1);
- $win.on("scroll",dealClass);
- function dealClass(isRemove){
- var top = $win.height() + $win.scrollTop();
- if(isRemove!=1) { //滾動頁面時的判斷,并添加class="visible"
- for (var i = 0,$elem; i < elmArr.length; i++) {
- $elem = $(elmArr[i]);
- if ($elem.data("ot") <= top) {
- $elem.addClass("visible");
- elmArr.splice(i, 1);
- --i;
- if(i<0) $win.off("scroll",dealClass);
- }
- }
- }else{ //初始化頁面時的判斷,并刪除class="visible"
- for (var i = 0,$elem; i < elmArr.length; i++) {
- $elem = $(elmArr[i]);
- if ($elem.data("ot") >= top) {
- $elem.removeClass("visible");
- }
- }
- }
- }
- })
- </script>
- </head>
- <body>
- <article>
- <section>123</section>
- </article>
- <div class="visible">
- <span>hello</span>
- </div>
- </body>
- </html>
效果如下:
其實這段代碼中涉及動畫的最關鍵的部分不外乎這兩行:
div.visible{-webkit-animation:ani-fade-in-up 2s ease forwards;animation:ani-fade-in-up 2s ease forwards;}
div.visible > span {-webkit-animation:ani-fade-in 1s 2s ease forwards;animation:ani-fade-in 1s 2s ease forwards;}
其中span動畫延遲2秒執行,執行過程為1s,另外兩者都使用了forwards來保持最終狀態。
另外在介紹wifi的地方還有一個有趣的循環動畫:
這是在animation中將其動畫執行次數設置為infinite :
- @-webkit-keyframes ani-circle-scale {
- 0% {
- -webkit-transform: scale(0);
- margin-left: 0
- }
- 45% {
- -webkit-transform: scale(1);
- margin-left: -999px;
- opacity: 1
- }
- 80% {
- opacity: 1
- }
- 100% {
- opacity: 0
- }
- }
- div{-webkit-animation:ani-circle-scale 8s ease-out forwards infinite;}
更多animation的知識點可點此查閱。
其它
在相機介紹頁面,小米還使用了video來展示一個小動畫(有些復雜點的展示效果直接使用小尺寸的視頻來展示也是個不錯的選擇):
此處代碼為:
- <video id="exporevideo" poster="http://img03.mifile.cn/webfile/images/2014/cn/goods/minote/camera/ca-49.png">
- <source src="http://img03.mifile.cn/webfile/images/2014/cn/goods/minote/camera/jingtou.mp4" type="video/mp4">
- <source src="http://img03.mifile.cn/webfile/images/2014/cn/goods/minote/camera/jingtou.webm" type="video/webm">
- <img src="http://img03.mifile.cn/webfile/images/2014/cn/goods/minote/camera/ca-49.png" alt="">
- </video>
兩個source分別匹配了mp4和webm格式的視頻文件,其中mp4是為了兼容IE9+和Safari,webm是為了兼容舊版的Firefox和Opera。除了mp4和webm,其實還有ogg格式可選,各瀏覽器對視頻格式的支持度可查看維基百科(非常詳盡)。
***的那張img圖片是用來優雅降級的,也就是不支持<video>標簽的瀏覽器會直接顯示為這張圖片。
小米在其基礎樣式中還對全體img使用了 -ms-interpolation-mode:bicubic 屬性,它可以讓IE下被縮小的圖片保持較高質量,而不是變得模糊、帶鋸齒。不過該樣式其實只對IE7有作用,因為IE8+的缺省值已設定為bicubic(IE7-下為nearest-neighbor ,圖片被縮放后質量會很差)。
另外小米對其樣式和腳本均做了混淆和壓縮處理,不過這已不是什么新奇的東西,只是使用了 grunt 或 gulp 等前端輔助工具罷了。
本篇就介紹到這里,實際上小米官網上還有很多本章未提及的有趣交互,有興趣的朋友可以去仔細摸索一番。
話說最近也是蠻拼的,博客越寫越長、越寫越晚,醉了。。。
共勉~