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 相关文章推荐
基于Jquery的文字滚动跑马灯插件(一个页面多个滚动区)
Jul 26 Javascript
Prototype源码浅析 String部分(二)
Jan 16 Javascript
JS常用函数使用指南
Nov 23 Javascript
jQuery中:radio选择器用法实例
Jan 03 Javascript
jquery实现拖拽调整Div大小
Jan 30 Javascript
jQuery给动态添加的元素绑定事件的方法
Mar 09 Javascript
javascript如何写热点图
Dec 08 Javascript
Web开发必知Javascript技巧大全
Feb 23 Javascript
js实现砖头在页面拖拉效果
Nov 20 Javascript
jQuery编写设置和获取颜色的插件
Jan 09 Javascript
Angular2 父子组件通信方式的示例
Jan 29 Javascript
element-ui 远程搜索组件el-select在项目中组件化的实现代码
Dec 04 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笔记之:有规律大文件的读取与写入的分析
2013/04/26 PHP
PHP给源代码加密的几种方法汇总(推荐)
2018/02/06 PHP
PHP微商城开源代码实例
2019/03/27 PHP
javascript String 对象
2008/04/25 Javascript
javascript基本语法分析说明
2008/06/15 Javascript
被jQuery折腾得半死,揭秘为何jQuery为何在IE/Firefox下均无法使用
2010/01/22 Javascript
js获取url参数代码实例分享(JS操作URL)
2013/12/13 Javascript
浅谈JavaScript Date日期和时间对象
2014/12/29 Javascript
微信小程序 数据访问实例详解
2016/10/08 Javascript
Angular中$broadcast和$emit的使用方法详解
2017/05/22 Javascript
解决使用Vue.js显示数据的时,页面闪现原始代码的问题
2018/02/11 Javascript
javaScript强制保留两位小数的输入数校验和小数保留问题
2018/05/09 Javascript
基于Vue实现关键词实时搜索高亮显示关键词
2018/07/21 Javascript
[00:35]TI7不朽珍藏III——寒冰飞龙不朽展示
2017/07/15 DOTA
python超简单解决约瑟夫环问题
2015/05/12 Python
python+selenium识别验证码并登录的示例代码
2017/12/21 Python
Python求解正态分布置信区间教程
2019/11/20 Python
Django中的session用法详解
2020/03/09 Python
python为Django项目上的每个应用程序创建不同的自定义404页面(最佳答案)
2020/03/09 Python
python开发实例之Python的Twisted框架中Deferred对象的详细用法与实例
2020/03/19 Python
Python3 shelve对象持久存储原理详解
2020/03/23 Python
TensorFlow2.1.0最新版本安装详细教程
2020/04/08 Python
浅谈keras使用预训练模型vgg16分类,损失和准确度不变
2020/07/02 Python
ProBikeKit美国官网:自行车套件,跑步和铁人三项套件
2016/10/13 全球购物
Nike德国官网:Nike.com (DE)
2018/11/13 全球购物
Tea Collection官网:一家位于旧金山的童装公司
2020/08/07 全球购物
毕业生自我鉴定
2013/11/05 职场文书
金融专业推荐信
2013/11/14 职场文书
孝敬父母的演讲稿
2014/05/14 职场文书
机械专业应届毕业生自荐书
2014/06/12 职场文书
国际贸易本科毕业生求职信
2014/09/26 职场文书
实习护士自荐信
2015/03/25 职场文书
2015年电厂工作总结范文
2015/05/13 职场文书
数据库连接池
2021/04/06 MySQL
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
2022/03/16 Redis