zoukankan      html  css  js  c++  java
  • JS+CSS实现的下拉刷新/上拉加载插件

    闲来无事,写了一个当下比较常见的下拉刷新/上拉加载的jquery插件,代码记录在这里,有兴趣将代码写成插件与npm包可以留言。

    体验地址:http://owenliang.github.io/pullToRefresh/

    项目地址:https://github.com/owenliang/pullToRefresh

    实现注意:

    • 利用transition做动画时,优先使用transform:translate取代top,后者动画流畅度存在问题。
    • 各移动浏览器对手势触摸的处理不同(简单罗列如下),但是下面的应对方案又会导致部分浏览器的overflow:scroll失效,总之难以兼容:
      • 微信浏览器下拉自带回弹动画:可以禁止document的touchmove事件默认处理行为。
      • 谷歌浏览器下拉自带刷新功能:利用属性touch-action: none可以禁掉。
    • 针对上述问题,我的建议是滚动一律用iscroll5插件模拟实现(非overflow:scroll),然后利用上面的方法禁掉浏览器的默认touchmove行为。
    • transition如果有多个属性,那么transitionend回调会为每个属性回调一次,因此遇到其中任意一个回调就应该把css和transitionend回调都删除掉。
    • 浏览器在执行JS代码时没有机会重绘UI,因此在使用transition的时候一定要注意把修改动画终止CSS的代码通过setTimeout延迟一会执行。

    贴代码上首页,欢迎留言交流,需一位有兴趣有时间的朋友合作,主要做2件事:

    1)插件改为NPM包。

    2)基于pullToRefresh库,开发类似"今日头条"的左右滑动UI。

    pullToRefresh.js:

    /**
     * 为指定的容器添加滚动条,支持下拉刷新与上拉加载功能
     * @param container 需要滚动的容器,要求设置css: position!=static,height=
     * @param option 配置项,详见下方defaultOption说明
     * @return 返回对象用于操控此区域,当前暴露了iscroll的refresh函数,当你在插件之外向滚动区域增加/删除内容后应该主动调用一次
     * @description
     *
     * 2017-03-29
     * 1)支持上拉加载
     * 2017-03-30
     * 1)改为jquery静态函数插件
     * 2)支持关闭下拉刷新或上拉加载
     */
    $.installPullToRefresh =
    function (container, option) {
        // 起始触摸位置
        var touchStartY = 0;
        // 起始图标位置
        var pullStartY = 0;
        // 当前的触摸事件
        var touchEvent = null;
        // 当前的刷新事件
        var refreshEvent = null;
        // 当前图标位置
        var curY = -55;
        // 当前的加载事件
        var loadEvent = null;
    
        // 默认参数
        var defaultOption = {
            // 刷新相关
            noRefresh: false, // 关闭下拉刷新特性
            pauseBound: 40,  // 触发刷新的位置(也是图标loading暂停的位置)
            lowerBound: 80, // 最大下拉到多少px
            loadImg: "load.png", // loading图片
            pullImg: "pull.png", // 下拉图片
            onRefresh: function (refreshDone) { // 刷新数据回调
                setTimeout(function() { // 默认不做任何事
                    refreshDone();
                }, 0);
            },
    
            // 加载相关
            noLoad: false, // 关闭上拉加载特性
            bottomHeight: 1, // 距离滚动条底部多少px发起刷新
            onLoad: function (loadDone) {
                setTimeout(function() {
                    loadDone();
                }, 0);
            },
        };
        var finalOption = $.extend(true, defaultOption, option);
    
        // 创建iscroll5滚动区域
        var iscroll = new IScroll(container, {
            bounce: false,
        });
    
        // 关闭上拉加载特性
        if (!finalOption.noLoad) {
            // 监听滚动结束事件,用于上拉加载
            iscroll.on('scrollEnd', function () {
                // 有滚动条的情况下,才允许上拉加载
                if (iscroll.maxScrollY < 0) { // maxScrollY<0表明出现了滚动条
                    var bottomDistance = (iscroll.maxScrollY - iscroll.y) * -1;
                    // 距离底部足够近,触发加载
                    if (bottomDistance <= finalOption.bottomHeight) {
                        // 当前没有刷新和加载事件正在执行
                        if (!loadEvent && !refreshEvent) {
                            loadEvent = {}; // 生成新的加载事件
                            finalOption.onLoad(function (error, msg) {
                                loadEvent = null; // 清理当前的加载事件
                                // 延迟重绘滚动条
                                setTimeout(function () {
                                    iscroll.refresh();
                                }, 0);
                            });
                        }
                    }
                }
            });
        }
    
        // 关闭下拉刷新特性
        if (!finalOption.noRefresh) {
            // 紧邻滚动区域,容纳刷新图标
            var pullContainer = $('<div class="pullContainer"></div>')
            // 创建小图标
            var pullToRefresh = $('<div class="pullToRefresh"><img src="' + finalOption.pullImg + '"></div>');
            // 保留小图标的快捷方式
            var pullImg = pullToRefresh.find("img");
            // 小图标加入到容器
            pullContainer.append(pullToRefresh);
            // 小图标容器添加到滚动区域之前
            $(container).before(pullContainer);
            // 预加载loadImg
            $('<img src="' + finalOption.loadImg + '">');
    
            // 设置transform的函数
            function cssTransform(node, content) {
                node.css({
                    '-webkit-transform' : content,
                    '-moz-transform'    : content,
                    '-ms-transform'     : content,
                    '-o-transform'      : content,
                    'transform'         : content,
                });
            }
    
            // 调整小图标位置,角度,透明度
            function goTowards(translateY, rotate, opcaticy) {
                // 更新当前小图标的位置,获取css(transform)比较麻烦,所以每次变更时自己保存
                curY = translateY;
    
                // 旋转图标(根据抵达lowerBound的比例旋转,最大转1圈)
                if (rotate === undefined) {
                    rotate = (curY / finalOption.lowerBound) * 360;
                }
                // 透明度根据抵达pauseBound的比例计算
                if (opcaticy === undefined) {
                    opcaticy = (curY / finalOption.pauseBound) * 1;
                    if (opcaticy > 1) {
                        opcaticy = 1;
                    }
                }
                // 改变位置和旋转角度
                cssTransform(pullToRefresh, "translateY(" + translateY + "px) translateZ(0)" + "rotateZ(" + rotate + "deg)");
                // 改变透明度
                pullToRefresh.css("opacity", opcaticy);
            }
    
            // 开启回弹动画
            function tryStartBackTranTop() {
                // 启动回弹动画
                pullToRefresh.addClass("backTranTop");
                // 判断是否触发刷新
                if (curY >= finalOption.pauseBound) {
                    goTowards(finalOption.pauseBound);
                    // 回弹动画结束发起刷新
                    pullToRefresh.on('transitionend webkitTransitionEnd oTransitionEnd', function (event) {
                        // 由于transitionend会对每个属性回调一次,所以只处理其中一个
                        if (event.originalEvent.propertyName == "transform") {
                            // 暂停动画
                            pullToRefresh.removeClass("backTranTop");
                            pullToRefresh.unbind();
                            // 透明度重置为1
                            goTowards(finalOption.pauseBound, undefined, 1);
                            // 切换图片为loading图
                            pullImg.attr("src", finalOption.loadImg);
                            // 因为anamition会覆盖transform的原因,使用top临时定位元素
                            pullToRefresh.addClass("loadingAnimation");
                            pullToRefresh.css("top", finalOption.pauseBound + "px");
                            // 回调刷新数据,最终应将refreshEvent传回校验
                            finalOption.onRefresh(function (error, msg) {
                                // 用户回调时DOM通常已经更新, 需要通知iscroll调整(官方建议延迟执行,涉及到浏览器重绘问题)
                                setTimeout(function () {
                                    iscroll.refresh();
                                }, 0);
                                // 重置角度,切换为pull图
                                goTowards(finalOption.pauseBound);
                                // 取消animation,重置top
                                pullToRefresh.removeClass("loadingAnimation");
                                pullToRefresh.css("top", "");
                                // 延迟过渡动画100毫秒,给浏览器重绘的机会
                                setTimeout(function () {
                                    // 切换为pull图
                                    pullImg.attr("src", finalOption.pullImg);
                                    // 恢复动画
                                    pullToRefresh.addClass("backTranTop");
                                    // 刷新完成
                                    refreshEvent = null;
                                    // 弹回顶部
                                    goTowards(-55);
                                }, 100);
                            });
                        }
                    });
                } else {
                    goTowards(-55); // 弹回顶部
                    refreshEvent = null; // 未达成刷新触发条件
                }
            }
    
            // 父容器注册下拉事件
            $(container).on("touchstart", function (event) {
                // 新的触摸事件
                touchEvent = {};
                // 有一个刷新事件正在进行
                if (refreshEvent) {
                    return;
                }
                // 只有滚动轴位置接近顶部, 才可以生成新的刷新事件
                if (iscroll.y < -1 * finalOption.lowerBound) {
                    return;
                }
    
                // 一个新的刷新事件
                refreshEvent = touchEvent;
    
                touchStartY = event.originalEvent.changedTouches[0].clientY;
                pullStartY = curY;
                // 如果存在,则关闭回弹动画与相关监听
                pullToRefresh.removeClass("backTranTop");
                pullToRefresh.unbind();
                // 切换为pull图
                pullImg.attr("src", finalOption.pullImg);
            }).on("touchmove", function (event) {
                // 在刷新未完成前触摸,将被忽略
                if (touchEvent != refreshEvent) {
                    return;
                }
                var touchCurY = event.originalEvent.changedTouches[0].clientY;
                var touchDistance = touchCurY - touchStartY; // 本次移动的距离
                var curPullY = pullStartY + touchDistance; // 计算图标应该移动到的位置
                // 向下不能拉出范围
                if (curPullY > finalOption.lowerBound) {
                    curPullY = finalOption.lowerBound;
                }
                // 向上不能拉出范围
                if (curPullY <= -55) {
                    curPullY = -55;
                }
                // 更新图标的位置
                goTowards(curPullY);
            }).on("touchend", function (event) {
                // 在刷新未完成前触摸,将被忽略
                if (touchEvent != refreshEvent) {
                    return;
                }
                // 尝试启动回弹动画
                tryStartBackTranTop();
            });
        }
    
        // 初始化iscroll
        setTimeout(function() {
            iscroll.refresh();
        }, 0);
    
        // 返回操作此区域的工具对象
        return {
            // 用户如果在下拉刷新之外修改了滚动区域的内容,需要主动调用refresh
            refresh: function() {
                // 延迟以便配合浏览器重绘
                setTimeout(function() {
                    iscroll.refresh();
                }, 0);
            },
            // 触发下拉刷新
            triggerPull: function() {
                // 正在刷新或者禁止刷新
                if (refreshEvent || finalOption.noRefresh) {
                    return false;
                }
                // 暂停可能正在进行的最终阶段回弹动画
                pullToRefresh.removeClass("backTranTop");
                // 小图标移动到lowerbound位置
                goTowards(finalOption.lowerBound);
                // 创建新的刷新事件,占坑可以阻止在setTimeout之前的触摸引起刷新
                refreshEvent = {};
                // 延迟到浏览器重绘
                setTimeout(function() {
                    tryStartBackTranTop();
                }, 100);
            },
        };
    };
    Contact GitHub API Training Shop Blog About
    © 2017 GitHub, Inc. Terms Privacy Security Status Help

    pullToRefresh.css:

    .pullToRefresh {
        position:absolute;
        left:0;
        right:0;
        margin:auto;
        width: 50px;
        height: 50px;
        z-index: 10;
        opacity: 1;
    
        transform:translateY(-55px) translateZ(0) rotateZ(0deg);
        -ms-transform:translateY(-55px) translateZ(0) rotateZ(0deg);     /* IE 9 */
        -moz-transform:translateY(-55px) translateZ(0) rotateZ(0deg);     /* Firefox */
        -webkit-transform:translateY(-55px) translateZ(0) rotateZ(0deg); /* Safari 和 Chrome */
        -o-transform:translateY(-55px) translateZ(0) rotateZ(0deg);     /* Opera */
    }
    .backTranTop
    {
        transition: transform 0.8s ease, opacity 0.8s ease;
        -moz-transition: transform 0.8s ease, opacity 0.8s ease; /* Firefox 4 */
        -webkit-transition: transform 0.8s ease, opacity 0.8s ease; /* Safari 和 Chrome */
        -o-transition: transform 0.8s ease, opacity 0.8s ease; /* Opera */
    }
    .pullContainer {
        position:relative;
    }
    .pullToRefresh img {
        display:block;
        width: 40px;
        height: 40px;
    
        /* 让img居中在.pullToRefresh中 */
        position: absolute;
        top: 0;
        bottom: 0;
        left:0;
        right:0;
        margin:auto;
    }
    /* loading旋转动画 */
    .loadingAnimation
    {
        animation: loadingFrame 1s infinite;
        -moz-animation: loadingFrame 1s infinite;    /* Firefox */
        -webkit-animation: loadingFrame 1s infinite;    /* Safari 和 Chrome */
        -o-animation: loadingFrame 1s infinite;    /* Opera */
    }
    @keyframes loadingFrame
    {
        from {
            transform: rotateZ(360deg);
        }
        to {
            transform: rotateZ(0deg);
        }
    }
    
    @-moz-keyframes loadingFrame /* Firefox */
    {
        from {
            transform: rotateZ(360deg);
        }
        to {
            transform: rotateZ(0deg);
        }
    }
    
    @-webkit-keyframes loadingFrame /* Safari 和 Chrome */
    {
        from {
            transform: rotateZ(360deg);
        }
        to {
            transform: rotateZ(0deg);
        }
    }
    
    @-o-keyframes loadingFrame /* Opera */
    {
        from {
            transform: rotateZ(360deg);
        }
        to {
            transform: rotateZ(0deg);
        }
    }
  • 相关阅读:
    洛谷 P2922 [USACO08DEC]秘密消息Secret Message
    HDU 1542 Atlantis
    洛谷 P2146 软件包管理器
    rabbitmq
    POJ——T2446 Chessboard
    洛谷—— P3375 【模板】KMP字符串匹配
    洛谷——P3370 【模板】字符串哈希
    POJ——T1860 Currency Exchange
    洛谷—— P3386 【模板】二分图匹配
    python(1)- 初识python
  • 原文地址:https://www.cnblogs.com/qq120848369/p/6651096.html
Copyright © 2011-2022 走看看