HTML5 活動宣傳頁「My Flyme 獨家記憶」開發實踐總結

年前放假的最後一天,咱們上線了「My Flyme 獨家記憶」 H5 活動宣傳頁。css

因種種緣由,直到放假前幾天,才忽然要求咱們參與並開始項目的前端部分。此時大概的狀況是:全部數據已計算完畢;後端接口已完成待聯調;交互視覺只出了不到四分之一(一共二十多個頁面);咱們平時以中後臺項目爲主,這種活動頁從未涉及。咱們真正介入時距離上線時間點只有四天,在這種背景下,已經談不上探討什麼樣的技術選型最佳,只能是越快越容易實現越好。因而拉上了全部用得上的前端成員一塊兒努力,最終算是在要求時間點上實現了上線目標。html

這種活動宣傳性質的頁面雖然用不上太複雜的邏輯,但也有不少後臺項目涉及不到的細節,讓咱們踩了很多的坑。這裏針對項目開發過程當中涉及的一些主要技術點做一下總結回顧。前端

「My Flyme 獨家記憶」(DEMO, 數據爲隨機制造):https://lzw.me/pages/demo/myf...webpack

share.jpg

1 項目特色

  • 多頁滑動效果,頁面多,動畫元素細節多,動畫效果簡單nginx

  • 我的頁(主頁面)需 Flyme 帳號登陸,自有應用內要儘可能實現免登陸git

  • 將會在魅族主流應用和社區裏推廣es6

  • 可分享到微信、微博等主流社交媒體github

2 Slider 實現

多頁翻屏滑動的效果有不少開源的實現,固然但願儘可能的簡單本身實現也不是很複雜。咱們選擇的是百度開源的 iSlider,iSlider 是一款很是優秀的翻頁滑動組件。除此以外,還有不少成熟的開源實現可選用:web

3 移動屏幕適配

移動端屏幕適配經常使用的方案有以下三種:

  • 固定高度,寬度自適應

  • 固定寬度/高度,viewport 縮放

  • rem 作寬度,viewport 縮放

3.1 高度優先、viewport 等比縮放適配方案

由於涉及多頁大量的動畫元素,只能是絕對定位來快速佈局,咱們採起了第二種方案:頁面以 320x640 做爲基礎大小布局,在移動端根據實際的頁面大小等比縮放。主要適配代碼參考:

+function () {
    function isMobile() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobi/i.test(navigator.userAgent);
    }

    function setScale() {
        var pageScale = 1;

        if (window.top !== window) {
            return pageScale;
        }

        var width = document.documentElement.clientWidth || 360;
        var height = document.documentElement.clientHeight || 640;
        if (width / height >= 360 / 640) {
            // 高度優先
            pageScale = height / 640;
        } else {
            pageScale = width / 360;
        }

        var content = 'width=' + 360 + ', initial-scale=' + pageScale 
          + ', maximum-scale=' + pageScale + ', user-scalable=no';
        document.getElementById('viewport').setAttribute('content', content);

        return pageScale;
    }

    if (isMobile()) {
        setScale();
    } else {
        try {
            document.getElementsByTagName('html')[0].classList.add('pc');
        } catch (e) {}
    }
}

這種方案以高度優先,以使得所有內容都可以出如今可視區域。但有一個問題是,在 webview 內寬高比大於 9/16,因而實際採用了高度的比例縮放,基礎寬度縮放後會小於屏幕寬度,因爲涉及動畫的元素採用了絕對定位,這致使這些元素顯示上偏左,右邊出現較多的空白。

3.2 絕對定位元素的微調方法

對於這種問題,咱們的@零零柒同窗想到了一種簡單快速的解決方案:取得基礎寬度與真實寬度的縮放比,將全部絕對定位的元素按照該縮放比從新計算 left 位移。主要代碼參考:

import $ from './libs/zepto';

const width = document.documentElement.clientWidth || 360;
const height = document.documentElement.clientHeight || 640;
let pageScale = 1;

if (width / height >= 360 / 640) {
    pageScale = height / 640;
}

const offset = (width - pageScale * 360) / 2;
// 每當頁面切換後調用
export function positionFix(dom) {
    if (pageScale === 1 || !offset) {
        return;
    }

    // 爲一個頁面
    const $dom = $(dom);

    if ($dom.hasClass('position-fixed')) {
        return;
    }

    $dom.find('.text-box i, img').forEach((dom, i) => {
        let $this = $(dom),
            left;

        // 只修改絕對定位的元素
        if ($this.css('position') !== 'absolute') {
            return;
        }

        left = +($this.css('left').replace('px', ''));

        $this.css('left', (left + offset) + 'px');
        // console.log($(dom).css('left'));
    });

    $dom.addClass('position-fixed');
}

3.3 設計稿圖片等比縮放方法

頁面縮放解決了不一樣屏幕大小的佈局一致性。另外涉及的一個問題是,設計稿圖片大小如何進行等比縮放?

這個問題也很簡單,圖片引用直接設置 width 以縮放到合適大小;雪碧圖上的圖片則按照 360x640 的頁面大小進行縮放:

  • 對於絕對定位的元素,使用 transform: scale(0.333) 進行變換縮放

  • 對於流式佈局的元素,使用 zomm(0.333) 進行縮放

3.4 快速佈局方法

還有一個值得一提的問題是,如何作到佈局元素與設計稿徹底一致?

這個問題的解決方法是:使用靜態的設計稿圖片做爲全屏背景,經過調整各元素到對應位置,從而實現快速定位。咱們的一位同窗給出了這個方法,而且給出了一個讓頁面元素可拖動並設置最終位置(left/right值)的 jQuery 插件,這使得咱們的頁面佈局變得簡單而高效。

4 HTML5 動畫實現

動畫實現方案通常來講能夠選擇CSS3 動畫、引入遊戲引擎或使用 svg/canvas。

4.1 CSS3 動畫

該方案技術成本簡單,任何前端開發者都能快速上手,但細節實現上工做量大。

從簡單快速開始的角度來講,CSS3 動畫是咱們的惟一選擇。使用 CSS3 動畫須要特別注意一點:修改 DOM 會致使頁面重繪,在移動端容易出現卡頓現象。因此應儘量避免使用會修改 DOM 的 css 屬性,只使用 transform 實現動畫變換效果。

咱們的頁面動畫都是循環運動的,所有須要對照動效設計逐一還原實現,花費了大量的人力和時間成本。實現上主要使用了 animationkeyframetransform 屬性。

此外,對於常見的入場顯示/滑入等動畫效果,只須要使用 transformtransition 便可實現需求。對於 css 動畫也有不少優秀而成熟的動畫庫可用,一些參考:

下面簡單介紹一下另外的兩種動畫實現方案。

4.2 引入遊戲引擎方案

使用 h5 遊戲引擎可大幅度下降工做量,可以相對容易地實現複雜動畫效果,但須要經驗避免入深坑,有較高學習成本,而且須要設計師深度配合。一些參考:

4.3 使用 svg/canvas 操做庫

使用 svg/canvas/webGL 實現的動畫效果會比較好,但實現工做量較大,對實踐經驗也有較高的要求。成熟的相關庫參考:

5 微信分享

因爲第一次作這種活動頁,沒有特別注意到微信內分享的問題,直到上線時才發現,分享出去的效果實在太難看,這致使幾位留守到最後的同事緊急探討協調方案,幾乎整晚沒睡覺。

5.1 微信 jssdk 分享 API

微信內開發應注意這幾點:

  • 下載微信開發者工具(或 TBS Studio),以調試微信內頁面

  • 須要經過認證的公衆號或訂閱號,取得微信 jssdk 分享接口所需的 appId 和 signature

  • 須要後端 API 管理 signature 簽名的生成與緩存

  • window.history.pushState/replaceState 修改了 URL 時須要從新生成 signature 簽名。因爲沒有仔細閱讀文檔並意識到這一點,在這個問題上坑了比較多的時間。

咱們最終協調到一個部門的訂閱號,並使用他們已實現了的後端 token 簽名生成 API 來實現 jssdk 的分享 API,在 nginx 層對該 API 代理轉發解決跨域安全性相關問題。微信分享主要代碼參考:

const isWeixinBrowser = /micromessenger/.test(navigator.userAgent.toLowerCase());
const wxJsdk = '//res.wx.qq.com/open/js/jweixin-1.1.0.js';
const jsApiList = ['checkJsApi', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo'];
let opts = {
    title: 'My Flyme 獨家記憶',
    desc: '當時光凝固,當回憶定格。回首2016 ,我與 Flyme 的點點滴滴都在這裏。這是屬於我和 Flyme 的獨家記憶。',
    link: '',
    imgUrl: ''
}, loadedwx = false;

function initShareEvent(wx) {
    const option = {
        ...opts,
        trigger: function (res) {console.log('trigger', res)},
        success: function (res) { console.log('已分享', res) },
        cancel: function (res) { console.log('已取消', res) },
        fail: function (res) { console.log(JSON.stringify(res)) } 
    };

    wx.onMenuShareAppMessage(option); // 分享給朋友
    wx.onMenuShareTimeline(option);   // 分享到朋友圈
    wx.onMenuShareQQ(option);         // 分享到QQ
    wx.onMenuShareWeibo(option);      // 分享到微博
    wx.onMenuShareQZone(option);      // 分享到QZone
}
function checkJsApi(wx) {
    wx.checkJsApi({
        jsApiList,
        success: () => initShareEvent(wx),
        error: err => console.log('checkJsApi error: ', err)
    });
}
function initConfig(wx) {
    // 特別注意,這裏 link 必須使用當前頁面的 URL 地址,不然會失敗!
    opts.link = encodeURIComponent(document.location.href.split('#')[0]);
    return $.ajax({
        url: '/wechat_api/get_js_ticket?&url=' + opts.link,
        dataType:'jsonp', //指定爲jsonp類型
        jsonp:'callback'
    }).done((res) => {
        wx.config({
            debug: process.env.NODE_ENV === 'development',
            appId: 'wx0000000000000000',
            nonceStr: res.data.nonceStr,
            timestamp: res.data.timestamp,
            signature: res.data.signature,
            jsApiList
        });
    });
}
export default options => {
    if (loadedwx || !isWeixinBrowser) { return }

    require([wxJsdk], (wx) => {
        loadedwx = true;
        $.extend(true, opts, options);
        initConfig(wx);
        wx.ready(() => checkJsApi(wx));
        wx.error((res) => console.error('出錯了:', res.errMsg));
    });
}

5.2 不走微信 jssdk 的取巧方法

微信分享 API 須要公衆號或訂閱號,臨時的活動開發可能來不及折騰,那麼一個折中的辦法是這樣的:在頁面頭部 img 標籤設置分享顯示的圖片,設置高度和寬度爲 0。示例:

<img src="assets/webp/share.webp" style="width:0;height:0;overflow:hidden" />

微信會提取頁面標題和第一張圖片,做爲朋友圈分享的標題和縮略圖。使用 jssdk 方式分享到朋友圈的效果也是隻有標題和縮略圖,因此效果上沒有區別。比較大的區別是,「發送給好友」時沒有描述,描述位置變成了頁面 URL 地址。

6 性能優化相關

因爲時間緊並且設計稿是逐步給到的,不少細節的優化都無法去作。最終上線的版本首屏大小約 1.3M,在弱網下的加載時間會比較久一些,可優化空間還比較大。這裏探討一下咱們主要考慮到的幾個點。

6.1 頁面資源異步加載

一共二十多個頁面,咱們按每頁一個 html 模板和一個 less 文件的方式,按頁面分工開發,在 index.html 頁面以 script 模板方式引入,由 fis3 實現模板嵌入。經過 ajax 拿到數據後,根據數據替換模板中的數據佔位符,並進行頁面切割,而後生成 iSlider 須要的數據配置項。這樣作的好處是 html 內容未寫入到 DOM 時,涉及的靜態資源圖片不會被加載。

iSlider 默認至少加載 3 個頁面,每一時刻也最多保存三個頁面實例。因而首屏加載了三個頁面,這正好符合咱們的目的。

另外要提到的一點是,弱網下不一樣圖片下載的時差較大,會使得不一樣位置的圖片動畫斷斷續續地出現。爲了不這種很差的效果,咱們使用了一個簡單的圖片預加載機制,在預加載完首屏涉及的圖片資源後才隱藏 loading 顯示頁面。

6.2 webp 支持

該項目涉及圖片資源 500 多張,只有手繪文字圖片作了雪碧圖處理。如今的移動端基本都支持 webp,使用 webp 是必須的。實際上使用 webp 後,圖片目錄的大小減少了 60%。

之前你們都是用智圖這種在線工具處理少許的圖片,搜索了一下,竟然沒有找到現成的批量生成 webp 的工具庫,因而寫了一個批量生成方法。這兩天整理完善了一下,算是造了一個小輪子,須要的同窗能夠關注下,地址在這裏:

webp 批量轉換:https://github.com/lzwme/webp-batch-convert

7 應用內登錄/分享

因爲要在 Flyme 自帶的近十個主要應用內做推廣入口,涉及到兩個問題:應用內分享和應用內免登陸。

在協調這一塊時發現,各應用都是獨自制定的各不相同的 webview 內相關 js 接口和規範,同一應用不一樣版本的實現也可能有差別,或者根本沒有相關實現;有規範的文檔也不夠齊全,而且都沒有示例參考;沒有各應用的開發測試版原本作調試。因而花了很多時間各類諮詢,踩了很多的坑,效果也仍是不盡人意,最終只在魅族瀏覽器上作到了指望的效果。

沒有統一規範,各自造輪子,因而這種跨部門跨應用的功能需求變得如此艱難。致使這種現象的存在因素不少,可知的一點是也和公司內前端人員處於邊緣化地位的現實有關。過去的一年裏,基礎技術支撐部門技術平臺作了一套 hybridApp 解決方案,@chemdemo 同窗還將 JSBridge 部分抽離開源了出來:https://github.com/chemdemo/hybrid-js。多是缺乏高層足夠的相關意識和支持力度,並無在各業務軟件內獲得普遍應用,反而主要靠內部前端圈間溝通傳播。不過這套方案爲了簡潔只實現了不多的通用 API 和可擴展方法,並無繼續實現各類業務適用的通用性擴展功能,天然也沒有咱們想要的應用內分享和 Flyme 免登陸這兩個功能。

對於這個問題有兩點總結:

  • 統一的公共 SDK 的重要性:避免重複造輪子,健壯且具備一致性的 API、完善的文檔。

  • 再好的文檔不如一個 demo

8 工程化問題

項目初期使用 webpack 進行構建,但因爲咱們平時的經驗以 fis3 爲主,webpack 過於靈活的配置特性使得一些工程化需求須要花時間探索。在咱們接手項目後一塊兒討論了一下,果斷轉爲熟悉的 fis3 構建體系。使用 fis3 主要解決的問題有:

  • less 編譯

  • es6 編譯

  • js/css 壓縮合並

  • 頁面模板嵌入

  • 發佈時 CDN 單獨域名的適配

  • 發佈時符合內部運維體系線上發佈規範的目錄路徑修正

相比較爲靈活的 webpackfis3 更注重流程化總體解決方案,簡單的數十行配置便可實現各類工程化需求。不過 fis3 的發展示在彷佛進入了一個瓶頸期/穩按期,社區中對於在 rollupwebpack 中大熱的 tree-shaking 等技術幾乎都沒有什麼反應。但願它不要沒落了,能有更多的創新吧。有兩點指望:走國際化路線,出英文文檔與社區支持,向國際頂級項目看進;跟進參考業界最新的工程化理念,若有必要出個 fis4 也何嘗不可。

9 運維發佈問題

因爲涉及到跨部門合做,也沒有太多的時間,項目起初放在了內部的 gitlab 平臺,沒有走 git+gerrit+jenkins+運維發佈平臺 這一內部完整的體系。這樣作在前期省去了項目建立、各類權限申請等一堆須要協調溝通的事情,開發協做效率也比較高,但到了發佈的階段就突顯出了問題:每次發佈都須要由運維人員手動操做,協調發布很花費時間。

另外靜態頁面也沒有獨立出來,想固然地簡單的扔到後端目錄中,結果致使前端的修改須要後端也必須做修改發佈,增長了先後端協調的時間成本。

因而,在首次發佈後又進行的幾回小迭代中,每次迭代發佈都涉及到多人手動協調,十分的浪費時間。

總結一句話:項目構建儘可能與已有的成熟的規範一致,以少走彎路。

10 其餘

最後列舉一些移動端 H5 開發可參考的內容:

小結

這是咱們第一次嘗試這種活動頁,在如此緊湊的時間節點下,沒有什麼高大上的東西,更多的是各類採坑嘗試的實踐過程。以上列舉的內容算是對本次開發實踐作一個總結記錄,採用的相關實現方案也可做後續參考。歡迎探討分享大家的經驗。

相關文章
相關標籤/搜索