JS+CSS实现下拉刷新/上拉加载插件


Posted in Javascript onMarch 31, 2017

闲来无事,写了一个当下比较常见的下拉刷新/上拉加载的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);
  }
}

以上所述是小编给大家介绍的JS+CSS实现下拉刷新/上拉加载插件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript DOM学习第一章 W3C DOM简介
Feb 19 Javascript
JS+CSS实现闪烁字体效果代码
Apr 05 Javascript
用jmSlip编写移动端顶部日历选择控件
Oct 24 Javascript
vue中mint-ui环境搭建详细介绍
Apr 06 Javascript
AngularJS双向绑定和依赖反转实例详解
Apr 15 Javascript
JavaScript判断输入是否为数字类型的方法总结
Sep 28 Javascript
vue2.0 如何把子组件的数据传给父组件(推荐)
Jan 15 Javascript
Vue中如何实现proxy代理
Apr 20 Javascript
Layui给数据表格动态添加一行并跳转到添加行所在页的方法
Aug 20 Javascript
Vue 实例事件简单示例
Sep 19 Javascript
openlayers4实现点动态扩散
Aug 17 Javascript
谈谈JavaScript中的函数
Sep 08 Javascript
ES6中Generator与异步操作实例分析
Mar 31 #Javascript
微信公众号菜单配置微信小程序实例详解
Mar 31 #Javascript
ES6中Iterator与for..of..遍历用法分析
Mar 31 #Javascript
node.js平台下的mysql数据库配置及连接
Mar 31 #Javascript
微信小程序 中wx.chooseAddress(OBJECT)实例详解
Mar 31 #Javascript
angular.JS实现网页禁用调试、复制和剪切
Mar 31 #Javascript
angular.js+node.js实现下载图片处理详解
Mar 31 #Javascript
You might like
PHP set_error_handler()函数使用详解(示例)
2013/11/12 PHP
Cygwin中安装PHP方法步骤
2015/07/04 PHP
PHP调试及性能分析工具Xdebug详解
2017/02/09 PHP
php获取linux命令结果的实例
2017/03/13 PHP
checkbox 多选框 联动实现代码
2008/10/22 Javascript
jquery无缝向上滚动实现代码
2013/03/29 Javascript
jquery.hotkeys监听键盘按下事件keydown插件
2014/05/11 Javascript
浅谈jQuery中对象遍历.eq().first().last().slice()方法
2014/11/26 Javascript
JavaScript Function函数类型介绍
2015/04/08 Javascript
Javascript 高阶函数使用介绍
2015/06/15 Javascript
使用JavaScript实现旋转的彩圈特效
2015/06/23 Javascript
jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点
2015/12/01 Javascript
jQuery插件FusionCharts实现的MSBar2D图效果示例【附demo源码】
2017/03/24 jQuery
ES6 Promise对象概念与用法分析
2017/04/01 Javascript
vue.js响应式原理解析与实现
2020/06/22 Javascript
Node.js系列之发起get/post请求(2)
2019/08/30 Javascript
Node 代理访问的实现
2019/09/19 Javascript
浅谈layui 绑定form submit提交表单的注意事项
2019/10/25 Javascript
Vue的Eslint配置文件eslintrc.js说明与规则介绍
2020/02/03 Javascript
为react组件库添加typescript类型提示的方法
2020/06/15 Javascript
Vue props中Object和Array设置默认值操作
2020/07/30 Javascript
在python中bool函数的取值方法
2018/11/01 Python
python多任务及返回值的处理方法
2019/01/22 Python
详解python编译器和解释器的区别
2019/06/24 Python
Python numpy线性代数用法实例解析
2019/11/15 Python
Python流程控制常用工具详解
2020/02/24 Python
Python爬虫之Selenium库的使用方法
2021/01/03 Python
HTML5 在canvas中绘制文本附效果图
2014/06/23 HTML / CSS
LookFantastic丹麦:英国美容护肤精品在线商城
2016/08/18 全球购物
美国葡萄酒网上商店:Martha Stewart Wine Co.
2019/03/17 全球购物
Unix如何添加新的用户
2014/08/20 面试题
机械工程师岗位职责
2014/06/16 职场文书
农林经济管理专业自荐信
2014/09/01 职场文书
项目负责人岗位职责
2015/02/15 职场文书
Win10/Win11 任务栏替换成经典样式
2022/04/19 数码科技
图神经网络GNN算法
2022/05/11 Python