5

需求

最近公司官网改版 手机端新的设计稿中有个轮播效果 如下图:

图片描述

这个轮播图要求有三个效果

  1. 左右滑动可以切换 不循环轮播 上面列表中的文案要求高亮的尽量居中 类似今日头条app那种滑动切换

  2. 上面列表中的文案点击 滑动到对应位置

  3. 列表文案共6个 超出容器可见宽度 要求实现拖动列表

解决

布局: 选择了手淘大漠老师的flexible布局方案

JS插件: 最后选择了jquery 为啥不用zepto zepto确实很小 但是jquery也仅仅相当于是一张图的大小 可以接受; 再有jquery还在不停的更新 而zepto则发展很慢 社区也不活跃;越来越多的人也开始渐渐倒戈jquery;

由于是移动端的效果 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插件 十分的方便

代码实现

现在开始实现代码

首先看下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


Aus0049
2.4k 声望231 粉丝

console.log(([][[]]+[])[+!![]]+([]+{})[!+[]+!![]])


引用和评论

0 条评论