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 相关文章推荐
extjs 列表框(multiselect)的动态添加列表项的方法
Jul 31 Javascript
JQuery 动画卷页 返回顶部 动画特效(兼容Chrome)
Feb 15 Javascript
jQuery代码优化 选择符篇
Nov 01 Javascript
javascript 中that的含义示例介绍
May 14 Javascript
Javascript中设置默认参数值示例
Sep 11 Javascript
JS防止网页被嵌入iframe框架的方法分析
Sep 13 Javascript
浅谈JS验证表单文本域输入空格的问题
Feb 14 Javascript
详谈angularjs中路由页面强制更新的问题
Apr 24 Javascript
jQuery制作全屏宽度固定高度轮播图(实例讲解)
Jul 08 jQuery
React Native验证码倒计时工具类分享
Oct 24 Javascript
解决vue attr取不到属性值的问题
Sep 18 Javascript
详解如何在JS代码中消灭for循环
Dec 11 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函数preg_match_all正则表达式的基本使用详细解析
2013/08/31 PHP
php获取域名的google收录示例
2014/03/24 PHP
PHP批量生成静态HTML的简单原理和方法
2014/04/20 PHP
thinkPHP+LayUI 流加载实现功能
2019/09/27 PHP
Thinkphp框架使用list_to_tree 实现无限级分类列出所有节点示例
2020/04/04 PHP
Cookie 注入是怎样产生的
2009/04/08 Javascript
JS删除数组元素的函数介绍
2013/03/27 Javascript
jquery中通过父级查找进行定位示例
2013/06/28 Javascript
jquery的ajax异步请求接收返回json数据实例
2014/06/16 Javascript
JavaScript实现信用卡校验方法
2015/04/07 Javascript
js实现二级菜单渐隐显示
2015/11/03 Javascript
详解angular 中的自定义指令之详解API
2017/06/20 Javascript
JS实现读取xml内容并输出到div中的方法示例
2018/04/19 Javascript
微信小程序拼接图片链接无底洞深入探究
2019/09/03 Javascript
解决vue-router 二级导航默认选中某一选项的问题
2019/11/01 Javascript
python中的全局变量用法分析
2015/06/09 Python
python 异常处理总结
2016/10/18 Python
python字典快速保存于读取的方法
2018/03/23 Python
python中实现数组和列表读取一列的方法
2018/04/03 Python
Python设计模式之适配器模式原理与用法详解
2019/01/15 Python
python设置随机种子实例讲解
2019/09/12 Python
python查询MySQL将数据写入Excel
2020/10/29 Python
CSS3实现红包抖动效果
2020/12/23 HTML / CSS
LA MER海蓝之谜美国官网:传奇面霜
2016/08/27 全球购物
应届生体育教师自荐信
2013/10/03 职场文书
汽车制造与装配专业自荐信范文
2014/01/02 职场文书
妇女儿童发展规划实施方案
2014/03/16 职场文书
儿童生日会策划方案
2014/05/15 职场文书
反邪教标语
2014/06/23 职场文书
七一建党日演讲稿
2014/09/05 职场文书
2015年医院后勤工作总结
2015/05/20 职场文书
婚宴父亲致辞
2015/07/27 职场文书
2016入党心得体会范文
2016/01/06 职场文书
导游词之临安白水涧
2019/11/05 职场文书
Python控制台输出俄罗斯方块移动和旋转功能
2021/04/18 Python
Vue3.0 手写放大镜效果
2021/07/25 Vue.js