使用Hammer製做移動端輪播圖

需求

最近公司官網改版 手機端新的設計稿中有個輪播效果 以下圖:javascript

圖片描述

這個輪播圖要求有三個效果css

  1. 左右滑動能夠切換 不循環輪播 上面列表中的文案要求高亮的儘可能居中 相似今日頭條app那種滑動切換html

  2. 上面列表中的文案點擊 滑動到對應位置java

  3. 列表文案共6個 超出容器可見寬度 要求實現拖動列表jquery

解決

佈局: 選擇了手淘大漠老師的flexible佈局方案git

JS插件: 最後選擇了jquery 爲啥不用zepto zepto確實很小 可是jquery也僅僅至關因而一張圖的大小 能夠接受; 再有jquery還在不停的更新 而zepto則發展很慢 社區也不活躍;愈來愈多的人也開始漸漸倒戈jquery;github

因爲是移動端的效果 iOS上使用click事件會有300ms延遲 體驗不佳
並且使用原生的tochstart touchmove等事件實現效果很麻煩
因此 須要使用支持移動端的 能夠幫助快速方便簡單的識別用戶手勢操做的插件
目前來開 只有一個比較好 hammer面試

Hammer.js

Hammer.js是目前手勢插件中的佼佼者 github上也是有1w5的start
其其實是對touchstart touchmove和touchend進行了封裝
封裝出tap swipe pan press rotate pinch 等常見手勢
而且每一個手勢還提供了對應的start move end函數
還有對應的jquery.hammer.js插件 十分的方便閉包

代碼實現

如今開始實現代碼 app

首先看下HTML機構

<div class="characteristic-light-box">
        <div class="header">
            <ul class="header-list" id="header-list" style="margin-left: 0">
                <li class="active"><a class="item" href="javascript:;">員工管理</a></li>
                <li><a class="item" href="javascript:;">招聘管理</a></li>
                <li><a class="item" href="javascript:;">排班考勤</a></li>
                <li><a class="item" href="javascript:;">薪酬社保</a></li>
                <li><a class="item" href="javascript:;">審批公告</a></li>
                <li><a class="item" href="javascript:;">組織權限</a></li>
            </ul>
        </div>
        <div class="body">
            <ul class="body-list" id="swipe-list" style="margin-left: 0">
                <li class="active">
                    <p class="title">員工管理</p>
                    <p class="subtitle">告別紙質檔案,採用更有效的信息系統,還有及時<br>的人事提醒和強大的人才挖掘功能</p>
                    <img class="screenshot" src="img/page-index-pro-yg.png">
                </li>
                <li>
                    <p class="title">招聘管理</p>
                    <p class="subtitle">跟多帳號說再見,一站掌控你的崗位、簡歷、面試<br>和人才庫</p>
                    <img class="screenshot" src="img/page-index-pro-zp.png">
                </li>
                <li>
                    <p class="title">排班考勤</p>
                    <p class="subtitle">手機/考勤機/外勤打卡/自主排班,隨需選擇,靈<br>活定義,還會自動對接薪資哦</p>
                    <img class="screenshot" src="img/page-index-pro-kq.png">
                </li>
                <li>
                    <p class="title">薪酬社保</p>
                    <p class="subtitle">基本+崗位+績效+津貼+考勤…薪酬結構再複雜,也<br>能算得又準又快</p>
                    <img class="screenshot" src="img/page-index-pro-xc.png">
                </li>
                <li>
                    <p class="title">審批公告</p>
                    <p class="subtitle">通知一鍵下發,規章隨時可查,手機自助審批,瞬<br>間提高人力工做的員工體驗</p>
                    <img class="screenshot" src="img/page-index-pro-sp.png">
                </li>
                <li>
                    <p class="title">組織權限</p>
                    <p class="subtitle">不管你是大集團公司仍是小創業團隊,是老闆、HR、<br>經理仍是BP,全力支持你的工做</p>
                    <img class="screenshot" src="img/page-index-pro-zz.png">
                </li>
            </ul>
        </div>
        <div class="foot">
            <span class="dot active"></span>
            <span class="dot"></span>
            <span class="dot"></span>
            <span class="dot"></span>
            <span class="dot"></span>
            <span class="dot"></span>
        </div>
    </div>

跟普通的輪播圖同樣
上面的叫header-list 是一個ul列表
body中也是一個ul列表
footer則是簡單的div羅列

下面是一步一步實現:

1.實現點擊切換效果

var header = $(".header-list");
var headerList = $(".header-list li").hammer();

// 頁面中使用了rem佈局 是爲了兼容各個尺寸的屏幕 因此這裏使用rem代替px佈局
headerList.on("tap", function(){
    var index = $(this).index();

    $("#swipe-list").animate({marginLeft: -(8.53333 * index) + "rem" }, 500);

    $(".foot span").removeClass("active").eq(index).addClass("active");

    headerList.removeClass("active").eq(index).addClass("active");

    if([2,3,4].indexOf(index) >= 0){
        header.animate({marginLeft: -(1.46667 * (index - 1)) + "rem"});
    }
});

點擊header-list中的li 分別使header body 和footer滑動到對應位置上
注意這裏的hammer用法
裏面[2,3,4]是根據需求 得出的那些li點擊時 header-list須要跟着滾動
注意這裏使用的rem佈局 全部尺寸都是rem爲單位
因此marginLeft賦值的時候 也須要使用rem 單位換算
第一步完成

2.增長左右滑動切換

var swipeListHammer = new Hammer($("#swipe-list")[0]);
var header = $(".header-list");
var headerList = $(".header-list li").hammer();
var footerList = $(".foot span");
var banner = $("#swipe-list");
var currentBannerIndex = 0; // 記錄當前顯示的那個tab

// 點擊滑動
headerList.on("tap", function(){
    var index = $(this).index();

    banner.animate({marginLeft: -(8.53333 * index) + "rem" }, 500);

    footerList.removeClass("active").eq(index).addClass("active");

    headerList.removeClass("active").eq(index).addClass("active");

    if([2,3,4].indexOf(index) >= 0){
        header.animate({marginLeft: -(1.46667 * (index - 1)) + "rem"});
    }
    currentBannerIndex = index;
});
// ←滑 banner向左走 margin-left 減少
swipeListHammer.on("swipeleft", function(e){
    // 最後一張 不可再右滑 因此是<5
    if(currentBannerIndex < 5){
        banner.animate({marginLeft: -(8.53333 * (currentBannerIndex + 1)) + "rem" }, 500);
        // foot
        footerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");
        // header滑動
        headerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");

        if([1,2,3].indexOf(currentBannerIndex) >= 0){
            header.animate({marginLeft: -(1.46667 * currentBannerIndex) + "rem"});
        }
        // 索引增長
        currentBannerIndex++;
    }
});

// 右滑 banner向右走 margin-left 增大
swipeListHammer.on("swiperight", function(e){
    if(currentBannerIndex > 0){
        banner.animate({marginLeft: -(8.53333 * (currentBannerIndex - 1)) + "rem" }, 500);
        // foot
        footerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");
        // header滑動
        headerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");

        if([4,3,2].indexOf(currentBannerIndex) >= 0){
            header.animate({marginLeft: -(1.46667 * (currentBannerIndex - 2)) + "rem"});
        }
        currentBannerIndex--;
    }
});

使用hammer提供的swipeleft等事件能夠識別出用戶左劃仍是右劃 十分方便
使用個變量存儲當前顯示的索引值
最後當滑動到最左或最右時 不可再滑動
第二步完成

3.當即執行函數/使用閉包

step 2 中爲了保存數據 減小查詢DOM次數 使用了幾個全局變量
這樣的話 污染全局變量 變量掛載在window對象上 很容易被人直接修改 這顯然是很差的
解決辦法:改變變量的scope 當即執行函數/使用閉包

當即執行函數
(function () {
    var swipeListHammer = new Hammer($("#swipe-list")[0]);
    var header = $(".header-list");
    var headerList = $(".header-list li").hammer();
    var footerList = $(".foot span");
    var banner = $("#swipe-list");
    var currentBannerIndex = 0; // 記錄當前顯示的那個tab

    // 點擊滑動
    headerList.on("tap", function(){
        var index = $(this).index();

        banner.animate({marginLeft: -(8.53333 * index) + "rem" }, 500);

        footerList.removeClass("active").eq(index).addClass("active");

        headerList.removeClass("active").eq(index).addClass("active");

        if([2,3,4].indexOf(index) >= 0){
            header.animate({marginLeft: -(1.46667 * (index - 1)) + "rem"});
        }
        currentBannerIndex = index;
    });
    // ←滑 banner向左走 margin-left 減少
    swipeListHammer.on("swipeleft", function(e){
        // 最後一張 不可再右滑 因此是<5
        if(currentBannerIndex < 5){
            banner.animate({marginLeft: -(8.53333 * (currentBannerIndex + 1)) + "rem" }, 500);
            // foot
            footerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");
            // header滑動
            headerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");

            if([1,2,3].indexOf(currentBannerIndex) >= 0){
                header.animate({marginLeft: -(1.46667 * currentBannerIndex) + "rem"});
            }
            // 索引增長
            currentBannerIndex++;
        }
    });

    // 右滑 banner向右走 margin-left 增大
    swipeListHammer.on("swiperight", function(e){
        if(currentBannerIndex > 0){
            banner.animate({marginLeft: -(8.53333 * (currentBannerIndex - 1)) + "rem" }, 500);
            // foot
            footerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");
            // header滑動
            headerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");

            if([4,3,2].indexOf(currentBannerIndex) >= 0){
                header.animate({marginLeft: -(1.46667 * (currentBannerIndex - 2)) + "rem"});
            }
            currentBannerIndex--;
        }
    });
})();

使用當即執行函數 也至關因而改變了函數的做用域
可是其有個缺點 函數執行後 不在可控 不能刪除也不能傳值
因此須要改進下 使用閉包

閉包
var swipeClosure = (function () {
    var swipeListHammer = new Hammer($("#swipe-list")[0]);
    var header = $(".header-list");
    var headerList = $(".header-list li").hammer();
    var footerList = $(".foot span");
    var banner = $("#swipe-list");
    var currentBannerIndex = 0; // 記錄當前顯示的那個tab

    return function(){
        // 點擊滑動
        headerList.on("tap", function(){
            var index = $(this).index();

            banner.animate({marginLeft: -(8.53333 * index) + "rem" }, 500);

            footerList.removeClass("active").eq(index).addClass("active");

            headerList.removeClass("active").eq(index).addClass("active");

            if([2,3,4].indexOf(index) >= 0){
                header.animate({marginLeft: -(1.46667 * (index - 1)) + "rem"});
            }
            currentBannerIndex = index;
        });
        // ←滑 banner向左走 margin-left 減少
        swipeListHammer.on("swipeleft", function(e){
            // 最後一張 不可再右滑 因此是<5
            if(currentBannerIndex < 5){
                banner.animate({marginLeft: -(8.53333 * (currentBannerIndex + 1)) + "rem" }, 500);
                // foot
                footerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");
                // header滑動
                headerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");

                if([1,2,3].indexOf(currentBannerIndex) >= 0){
                    header.animate({marginLeft: -(1.46667 * currentBannerIndex) + "rem"});
                }
                // 索引增長
                currentBannerIndex++;
            }
        });

        // 右滑 banner向右走 margin-left 增大
        swipeListHammer.on("swiperight", function(e){
            if(currentBannerIndex > 0){
                banner.animate({marginLeft: -(8.53333 * (currentBannerIndex - 1)) + "rem" }, 500);
                // foot
                footerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");
                // header滑動
                headerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");

                if([4,3,2].indexOf(currentBannerIndex) >= 0){
                    header.animate({marginLeft: -(1.46667 * (currentBannerIndex - 2)) + "rem"});
                }
                currentBannerIndex--;
            }
        });
    }
})();
swipeClosure();

使用閉包的話 第一次取dom的變量存到閉包中去 使得外接函數沒法直接修改
避免了變量污染 並且函數能夠刪除 能夠傳遞
第三步完成

4.增長header的拖拽

需求又改了 此次用戶以爲上面的header應該拖動 可是不能拖動 要加上
此次須要使用拖拽功能 直接使用hammer封裝好的pan事件

var swipeClosure = (function () {
    var swipeListHammer = new Hammer($("#swipe-list")[0]);
    var header = $(".header-list");
    var pinchHammer = new Hammer(header[0]);
    var headerList = $(".header-list li").hammer();
    var footerList = $(".foot span");
    var banner = $("#swipe-list");
    var currentBannerIndex = 0; // 記錄當前顯示的那個tab
    var rem = parseInt($("html").css("fontSize"));

    return function(){
        // 點擊滑動
        headerList.on("tap", function(){
            var index = $(this).index();

            banner.animate({marginLeft: -(8.53333 * index) + "rem" }, 500);

            footerList.removeClass("active").eq(index).addClass("active");

            headerList.removeClass("active").eq(index).addClass("active");

            if([2,3,4].indexOf(index) >= 0){
                header.animate({marginLeft: -(1.46667 * (index - 1)) + "rem"});
            }
            currentBannerIndex = index;
        });
        // ←滑 banner向左走 margin-left 減少
        swipeListHammer.on("swipeleft", function(e){
            // 最後一張 不可再右滑 因此是<5
            if(currentBannerIndex < 5){
                banner.animate({marginLeft: -(8.53333 * (currentBannerIndex + 1)) + "rem" }, 500);
                // foot
                footerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");
                // header滑動
                headerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");

                if([1,2,3].indexOf(currentBannerIndex) >= 0){
                    header.animate({marginLeft: -(1.46667 * currentBannerIndex) + "rem"});
                }
                // 索引增長
                currentBannerIndex++;
            }
        });

        // 右滑 banner向右走 margin-left 增大
        swipeListHammer.on("swiperight", function(e){
            if(currentBannerIndex > 0){
                banner.animate({marginLeft: -(8.53333 * (currentBannerIndex - 1)) + "rem" }, 500);
                // foot
                footerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");
                // header滑動
                headerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");

                if([4,3,2].indexOf(currentBannerIndex) >= 0){
                    header.animate({marginLeft: -(1.46667 * (currentBannerIndex - 2)) + "rem"});
                }
                currentBannerIndex--;
            }
        });

        // 滑動header
        pinchHammer.on("pan", function (e) {
            // e.deltaX就是偏移量
            var current = parseInt(header.css("marginLeft"));
            // 跟當前作差
            var result = current + e.deltaX / 10;

            // px2rem !!!
            result = result / rem;

            // 上下限制 左右拉倒最大值就不能在拉了
            if(result <= -5.1){
                result = -5.1;
            } else if (result >= 0) {
                result = 0;
            }

            header.css("marginLeft", result + "rem");
        });
    }
})();
swipeClosure();

注意jq.css()方法取出來的值 單位都是px 最後還要轉成rem使用
第四步完成

你覺得這就完成了嗎 no 要作一條有信仰的鹹魚
這麼生硬的效果顯然不能使人滿意 看看蘋果針對這種極限問題怎麼處理的
蘋果設備上頁面針對到上限或者下線的時候 並非不能拉 而是愈來愈難 最後鬆手的時候 回到極限
這種實現方法也很簡單 就是相似北京地鐵每月消費滿100 以後消費8折 滿150 5折一個道理 減小加速度

5.增長鬆手 彈性效果

var swipeClosure = (function () {
    var swipeListHammer = new Hammer($("#swipe-list")[0]);
    var header = $(".header-list");
    var pinchHammer = new Hammer(header[0]);
    var headerList = $(".header-list li").hammer();
    var footerList = $(".foot span");
    var banner = $("#swipe-list");
    var currentBannerIndex = 0; // 記錄當前顯示的那個tab
    var rem = parseInt($("html").css("fontSize"));

    return function(){
        // 點擊滑動
        headerList.on("tap", function(){
            var index = $(this).index();

            banner.animate({marginLeft: -(8.53333 * index) + "rem" }, 500);

            footerList.removeClass("active").eq(index).addClass("active");

            headerList.removeClass("active").eq(index).addClass("active");

            if([2,3,4].indexOf(index) >= 0){
                header.animate({marginLeft: -(1.46667 * (index - 1)) + "rem"});
            }
            currentBannerIndex = index;
        });
        // ←滑 banner向左走 margin-left 減少
        swipeListHammer.on("swipeleft", function(e){
            // 最後一張 不可再右滑 因此是<5
            if(currentBannerIndex < 5){
                banner.animate({marginLeft: -(8.53333 * (currentBannerIndex + 1)) + "rem" }, 500);
                // foot
                footerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");
                // header滑動
                headerList.removeClass("active").eq(currentBannerIndex + 1).addClass("active");

                if([1,2,3].indexOf(currentBannerIndex) >= 0){
                    header.animate({marginLeft: -(1.46667 * currentBannerIndex) + "rem"});
                }
                // 索引增長
                currentBannerIndex++;
            }
        });

        // 右滑 banner向右走 margin-left 增大
        swipeListHammer.on("swiperight", function(e){
            if(currentBannerIndex > 0){
                banner.animate({marginLeft: -(8.53333 * (currentBannerIndex - 1)) + "rem" }, 500);
                // foot
                footerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");
                // header滑動
                headerList.removeClass("active").eq(currentBannerIndex - 1).addClass("active");

                if([4,3,2].indexOf(currentBannerIndex) >= 0){
                    header.animate({marginLeft: -(1.46667 * (currentBannerIndex - 2)) + "rem"});
                }
                currentBannerIndex--;
            }
        });

        // 滑動header
        pinchHammer.on("pan", function (e) {
            // e.deltaX就是偏移量
            var current = parseInt(header.css("marginLeft"));
            // 跟當前作差
            var result = current + e.deltaX / 10;

            // px2rem !!!
            result = result / rem;

            // 伸縮效果 極限值是 0 和 -5.1 
            // 縮放範圍是 0.6 這些是試出來的 合理便可
            if(result <= -5.7){
                result = (result + 5.7) / 2 - 5.7;
            }

            if(result >= 0.6){
                result = (result - 0.6) / 2 + 0.6;
            }

            header.css("marginLeft", result + "rem");
        });

        pinchHammer.on("panend", function (e) {
            var current = parseFloat(header.css("marginLeft"));

            // px2rem
            current = current / rem;

            // 上下限制
            if(current <= -5.1){
                current = -5.1;
            } else if (current >= 0) {
                current = 0;
            }

            header.animate({marginLeft: current + "rem"}, 300);
        });
    }
})();
swipeClosure();

這裏面的數字 好比5.1 等都是寫css時測出來的值 並非固定值
第五步完成

總結

通過這個小例子 我知道了使用hammer能夠比較方便的識別用戶手勢
其還有縮放和旋轉等雙指操做的手勢可用
而且他在touchmove的event上又對event進行了封裝 能夠根據須要看看
最後項目源碼在github

相關文章
相關標籤/搜索