纯JavaScript简单实现移动端网页的上拉加载、下拉刷新

news2024/9/29 5:31:56

公司要在安卓软件中,添加H5网页包,实现订单卡片列表,要求有上拉加载、下拉刷新的功能。
经过搜索资料后,实现如下:

创建一个类PullData

class PullData() {
	/**
	  * 类的构造函数,在new PullData({xx: 'xxx'})时,参数会从consctructor中传入PullData类中
	  * options就是传入的{xx: 'xxx'},
	  * 先用一个变量将传入的参数存起来
	  */ 
	constructor(options) {
		this.data = options;
	}
}

实现订单列表的上拉、下拉,需要操作 dom 节点,所以我们要传入一个dom进行操作。

class PullData() {
	constructor(options) {
		this.data = options;
		// 用domWrapper来存储dom节点,如果没有传入就手动设置为body节点
		if(!options.dom) {
	      // 如果没有传入dom,默认是body
	      this.domWrapper = document.body;
	    } else {
	      // 传入的dom必须是 Nodelist 节点,或者是属性选择器字符串
	      this.domWrapper = typeof options.dom === 'string' ? document.querySelector(options.dom) : options.dom;
	    }
	}
}

除了订单列表的dom节点,还要初始化显示刷新中...加载中...文案的dom节点,因为我的订单列表是铺整页的,所以写的刷新、加载用 position: fixed 分别在页面顶部、页面底部。
在这里插入图片描述

class PullData() {
	constructor(options) {
		this.data = options;
		// 用domWrapper来存储dom节点,如果没有传入就手动设置为body节点
		if(!options.dom) {
	      // 如果没有传入dom,默认是body
	      this.domWrapper = document.body;
	    } else {
	      // 传入的dom必须是 Nodelist 节点,或者是属性选择器字符串
	      this.domWrapper = typeof options.dom === 'string' ? document.querySelector(options.dom) : options.dom;
	    }
	    // 调用初始化方法,进行数据、节点初始化等
	    this.init();
	}
	// 初始化
	init() {
	   this.renderTextDom();
	 }
	 // 创建渲染【刷新】【加载】文案所在的节点
	 renderTextDom() {
	   // 刷新提示语的节点
	   let refreshTextDom = document.createElement('div');
	   refreshTextDom.id = "refresh-text";
	   refreshTextDom.innerHTML = "刷新中...";
	   refreshTextDom.style = "display:none;position:fixed;top:0.2rem;left: 50%;transform: translateX(-50%);";
	   // 加载提示语的节点
	   let loadTextDom = document.createElement('div');
	   loadTextDom.id = "load-text";
	   loadTextDom.innerHTML = "加载中...";
	   loadTextDom.style = "display:none;position:fixed;bottom:0.2rem;left: 50%;transform: translateX(-50%);";
	
	   document.body.appendChild(refreshTextDom);
	   document.body.appendChild(loadTextDom);
	 }
}

上拉、下拉要通过touchstart、touchmove、touchend去监听用户的触摸动作。在初始化的时候,同时开启监听事件

init() {
	// 触摸开始事件
    this.domWrapper.addEventListener('touchstart', function (e) {
		// 监听操作
	}, false);

    // 触摸移动时间
    this.domWrapper.addEventListener('touchmove', function (e) {
		// 监听操作
	}, false);

    // 触摸结束事件
    this.domWrapper.addEventListener("touchend", function (e) {
		// 监听操作
	}, false);
}

用户不需要使用这个上拉下拉功能后,需要清除监听事件,否则监听事件只增不减,就会影响浏览器性能。所以在开启监听事件时,方法不能使用匿名函数,否则无法销毁对应的监听事件。

init() {
    this.renderTextDom();
    // 触摸开始事件
    this.domWrapper.addEventListener('touchstart', this.start, false);
    // 触摸移动时间
    this.domWrapper.addEventListener('touchmove', this.move, false);
    // 触摸结束事件
    this.domWrapper.addEventListener("touchend", this.end , false)
  }
  // 触摸开始事件回调
  start(e) {}
  // 触摸移动事件回调
  move (e) {}
  // 触摸结束事件回调
  end(e) {}
  // 销毁监听函数
  destroy() {
    this.domWrapper.removeEventListener('touchmove', this.move);
    this.domWrapper.removeEventListener('touchstart', this.start);
    this.domWrapper.removeEventListener('touchend', this.end);
  }

注意: addEventListener监听 this.domWrapper.addEventListener('touchstart', this.start, false);,此时的 this.start 函数内部的this指向的是 this.domWrapper,如果我要在this.start内部调用PullData内部的方法 this.beforeAction,就会报错Uncaught TypeError: this.beforeAction is not a function。所以我们要修正这些方法的this指向

class PullData() {
	constructor(options) {
		// ...
		
		// 将this.scroll 的this指向 PullData 这个类(原来指向nodelist节点)
    	this.scroll = this.scroll.bind(this); 
		// 将this.start 的this指向 PullData 这个类(原来指向nodelist节点)
    	this.start = this.start.bind(this); 
    	// 将this.move 的this指向 PullData 这个类(原来指向nodelist节点)
    	this.move = this.move.bind(this); 
    	// 将this.end 的this指向 PullData 这个类(原来指向nodelist节点)
    	this.end = this.end.bind(this); 
		
	    // 调用初始化方法,进行数据、节点初始化等
	    this.init();
	}
	// ...
}

touchstart、touchmove、touchend监听回调事件

开始时,记录手指/鼠标点击触碰到屏幕的位置,移动时,通过translateY修改下拉/上拉时卡片移动的距离,手指/鼠标松开后,将拉动的距离逐渐恢复到translateY(0px)

// 构造函数
constructor(options) {
	// ...
	this.rule = options.rule || 50; // 定义触发加载/刷新事件的拉伸长度
	this.startY = 0; // 记录鼠标/手指点击的位置
	this.moveY = 0; // 记录鼠标/手指移动的位置
	this.distance = 0; // 记录鼠标/手指移动的距离
	this.clientHeight = 0; // 滚动的可视区
	this.scrollTop = 0; // 滚动的距离
	this.scrollHeight = 0; // 滚动的总高度
	// 上拉加载事件回调函数
	this.pullUpCallback = options.pullUpCallback.bind(this) || function() {};
	// 下拉刷新事件回调函数
	this.pullDownCallback = options.pullDownCallback.bind(this) || function() {};
	// ...
}
// 触摸开始事件回调
  start (e) {
    this.startY = e.touches[0].screenY - this.distance;
  }

  // 触摸移动事件回调
  move (e) {
    e.stopPropagation();
    this.moveY = e.touches[0].screenY;
    this.distance = Math.floor(this.moveY - this.startY) / 5;
    this.beforeAction(this.distance);
    // 只有在触顶或者触底时,才操作domWrapper的样式
    if(this.scrollTop <= 0 || this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
      // 这一步才让人有被“拉”下来/上去的视觉效果
      this.domWrapper.style.transform = `translateY(${this.distance}px)`
    }
  }

  // 触摸结束事件回调
  end(e) {
    if(this.timer) clearInterval(this.timer);
    this.timer = setInterval(() => {
      if(Math.floor(this.distance) < 0 || Math.ceil(this.distance) < 10) {
        // 移动距离恢复到 10 以内时,将domWrapper的translateY设置为0,清除定时器
        this.distance = 0;
        // 这一步是“回弹到原位”的视觉效果
        this.domWrapper.style.transform = `translateY(0px)`;
        clearInterval(this.timer);
      } else {
        // 每10ms 减去 this.distance/10的距离
        this.distance -= this.distance / 10;
        // 只有在触顶或者触底时,才操作domWrapper的样式
        if(this.scrollTop <= 0 || this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
          // 这一步是“回弹”过程的视觉效果
          this.domWrapper.style.transform = `translateY(${this.distance}px)`;
        }
      }
    }, 10);

    // 触顶时才触发刷新回调,避免touchmove滚动过程中显示相关提示、接口请求
    if(this.scrollTop <= 0) {
      if(this.distance > this.rule) {
        this.refresh && this.refresh(this.pullDownCallback);
      }
    }
    // 触底时才触发加载回调,避免touchmove滚动过程中显示相关提示、接口请求
    if(this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
      if(this.distance < -this.rule) {
        this.loading && this.loading(this.pullUpCallback);
      }
    }
  }
  // UI处理
  beforeAction ( distance) {
    // 在列表触顶时,才显示【刷新中】的文案
    if(this.scrollTop - this.rule <= 0) {
      if (distance > this.rule) {
          var el = document.getElementById('refresh-text')
          el.innerHTML = '<p>刷新中...</p>'
          el.style.display = 'block'
      } else {
          document.getElementById('refresh-text').style.display = 'none'
      }
    }
    // 在列表触底(距离底部this.rule高度)时,才显示【加载中】的文案
    if(this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
      if (distance < -this.rule) {
          document.getElementById('load-text').style.display = 'block'
      } else {
          document.getElementById('load-text').style.display = 'none'
      }
    }
 }

完整代码:

// 上拉加载,wrapper里必须只有一个子元素,值举例:"#xxid"
export class PullData {
  constructor(options) {
    this.data = options;
    if(!options.dom) {
      // 如果没有传入dom,默认是body
      this.domWrapper = document.body;
    } else {
      // 传入的dom必须是 Nodelist 节点,或者是属性选择器字符串
      this.domWrapper = typeof options.dom === 'string' ? document.querySelector(options.dom) : options.dom;
    }
    this.rule = options.rule || 50; // 定义触发加载/刷新事件的拉伸长度
    this.startY = 0; // 记录鼠标/手指点击的位置
    this.moveY = 0; // 记录鼠标/手指移动的位置
    this.distance = 0; // 记录鼠标/手指移动的距离
    this.clientHeight = 0; // 滚动的可视区
    this.scrollTop = 0; // 滚动的距离
    this.scrollHeight = 0; // 滚动的总高度
    // 上拉加载事件回调函数
    this.pullUpCallback = options.pullUpCallback.bind(this) || function() {};
    // 下拉刷新事件回调函数
    this.pullDownCallback = options.pullDownCallback.bind(this) || function() {};
    
    // 将this.scroll 的this指向 PullData 这个类(原来指向nodelist节点)
    this.scroll = this.scroll.bind(this); 
    // 将this.start 的this指向 PullData 这个类(原来指向nodelist节点)
    this.start = this.start.bind(this); 
    // 将this.move 的this指向 PullData 这个类(原来指向nodelist节点)
    this.move = this.move.bind(this); 
    // 将this.end 的this指向 PullData 这个类(原来指向nodelist节点)
    this.end = this.end.bind(this); 

    this.timer = null; // 上拉/下拉样式回弹的定时器

    this.init(); // 初始化PullData的数据和节点
  }
  // 初始化
  init() {
    this.renderTextDom();
    this.scrollTop = this.domWrapper.scrollTop;
    this.scrollHeight = this.domWrapper.scrollHeight;
    this.clientHeight = this.domWrapper.clientHeight;
    // 滚动事件
    this.domWrapper.addEventListener('scroll', this.scroll, false);

    // 触摸开始事件
    this.domWrapper.addEventListener('touchstart', this.start, false);

    // 触摸移动时间
    this.domWrapper.addEventListener('touchmove', this.move, false);

    // 触摸结束事件
    this.domWrapper.addEventListener("touchend", this.end , false)
  }
  // 创建渲染【刷新】【加载】文案所在的节点
  renderTextDom() {
    // 刷新提示语的节点
    let refreshTextDom = document.createElement('div');
    refreshTextDom.id = "refresh-text";
    refreshTextDom.innerHTML = "刷新中...";
    refreshTextDom.style = "display:none;position:fixed;top:0.2rem;left: 50%;transform: translateX(-50%);";
    // 加载提示语的节点
    let loadTextDom = document.createElement('div');
    loadTextDom.id = "load-text";
    loadTextDom.innerHTML = "加载中...";
    loadTextDom.style = "display:none;position:fixed;bottom:0.2rem;left: 50%;transform: translateX(-50%);";

    document.body.appendChild(refreshTextDom);
    document.body.appendChild(loadTextDom);
  }
  // 滚动事件回调
  scroll(e) {
    this.scrollTop = e.target.scrollTop;
    this.scrollHeight = e.target.scrollHeight;
  }
  // 触摸开始事件回调
  start (e) {
    this.startY = e.touches[0].screenY - this.distance;
  }

  // 触摸移动事件回调
  move (e) {
    e.stopPropagation();
    this.moveY = e.touches[0].screenY;
    this.distance = Math.floor(this.moveY - this.startY) / 5;
    this.beforeAction(this.distance);
    // 只有在触顶或者触底时,才操作domWrapper的样式
    if(this.scrollTop <= 0 || this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
      this.domWrapper.style.transform = `translateY(${this.distance}px)`
    }
  }

  // 触摸结束事件回调
  end(e) {
    if(this.timer) clearInterval(this.timer);
    this.timer = setInterval(() => {
      if(Math.floor(this.distance) < 0 || Math.ceil(this.distance) < 10) {
        // 移动距离恢复到 10 以内时,将domWrapper的translateY设置为0,清除定时器
        this.distance = 0;
        this.domWrapper.style.transform = `translateY(0px)`;
        clearInterval(this.timer);
      } else {
        // 每10ms 减去 this.distance/10的距离
        this.distance -= this.distance / 10;
        // 只有在触顶或者触底时,才操作domWrapper的样式
        if(this.scrollTop <= 0 || this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
          this.domWrapper.style.transform = `translateY(${this.distance}px)`;
        }
      }
    }, 10);

    // 触顶时才触发刷新回调,避免touchmove滚动过程中显示相关提示、接口请求
    if(this.scrollTop <= 0) {
      if(this.distance > this.rule) {
        this.refresh && this.refresh(this.pullDownCallback);
      }
    }
    // 触底时才触发加载回调,避免touchmove滚动过程中显示相关提示、接口请求
    if(this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
      if(this.distance < -this.rule) {
        this.loading && this.loading(this.pullUpCallback);
      }
    }
  }
  // 刷新逻辑在此处理
  refresh (callback) {
    callback && callback();
    var el = document.getElementById('refresh-text')
    el.innerHTML = '<p>刷新成功!</p>'
    setTimeout(() => {el.style.display = 'none'}, 300)
  }
  // 加载逻辑在此处理
  loading (callback) {
    callback && callback();
    setTimeout(() => {
        document.getElementById('load-text').style.display = 'none'
    },300)
  }
  // UI处理
  beforeAction ( distance) {
    // 在列表触顶时,才显示【刷新中】的文案
    if(this.scrollTop - this.rule <= 0) {
      if (distance > this.rule) {
          var el = document.getElementById('refresh-text')
          el.innerHTML = '<p>刷新中...</p>'
          el.style.display = 'block'
      } else {
          document.getElementById('refresh-text').style.display = 'none'
      }
    }
    // 在列表触底(距离底部this.rule高度)时,才显示【加载中】的文案
    if(this.scrollTop + this.clientHeight + this.rule >= this.scrollHeight) {
      if (distance < -this.rule) {
          document.getElementById('load-text').style.display = 'block'
      } else {
          document.getElementById('load-text').style.display = 'none'
      }
    }
  }
  // 销毁监听函数
  destroy() {
    this.domWrapper.removeEventListener('scroll', this.scroll);
    this.domWrapper.removeEventListener('touchmove', this.move);
    this.domWrapper.removeEventListener('touchstart', this.start);
    this.domWrapper.removeEventListener('touchend', this.end);
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/687897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

尚硅谷甄选--(暂时不更新,实习,后期有时间更)

文章目录 搭建后台管理系统模板项目的资源地址项目初始化2.1.1环境准备2.1.2初始化项目2.2项目配置一、eslint配置1.1vue3环境代码校验插件1.2修改.eslintrc.cjs配置文件1.3.eslintignore忽略文件1.4运行脚本 二、配置**prettier**2.1安装依赖包2.2.prettierrc.json添加规则2.3…

OpenCV——总结《车牌识别》

1.图片中的hsv hsv提取蓝色部分 # hsv提取蓝色部分 def hsv_color_find(img):img_copy img.copy()cv2.imshow(img_copy, img_copy)"""提取图中的蓝色部分 hsv范围可以自行优化cv2.inRange()参数介绍&#xff1a;第一个参数&#xff1a;hsv指的是原图第二个参…

阿里老员工吐槽:部门来了个“卷”王同事,我们都要跟着加班..

随着IT互联网热愈演愈烈&#xff0c;大批应届生选择毕业后进入IT圈&#xff0c;还有另一批打工人冲着高薪福利待遇转行IT行业&#xff0c;越来越多人涌入程序员大军中。加之互联网行业的火爆&#xff0c;催生了大量程序员岗位&#xff0c;门槛也较之前来说越来越低了&#xff0…

Python采集商品数据信息,看看一般怎样销量会多

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: python 3.8 >>>>>> 运行代码 pycharm 2022.3 >>>>>> 辅助敲代码 模块使用: selenium >>> pip install selenium3.141.0 指定版本安装 <模拟人的行为去操…

《论文阅读07》Segment Anything in 3D with NeRFs

一、论文 研究领域&#xff1a;图像分割(3D)论文&#xff1a;Segment Anything in 3D with NeRFsSubmitted on 24 Apr 2023 (v1), last revised 1 Jun 2023 (this version, v3)Computer Vision and Pattern Recognition (cs.CV)nvos数据集论文链接 二、论文概要 三、全文翻译 …

通用vs垂直?热门AI大模型你更看好哪个?(附免费资源)

自ChatGPT发布以来&#xff0c;通用大模型就仿佛坐了火箭&#xff0c;短短几个月的时间&#xff0c;各大企业便争相发布自己的大模型&#xff0c;这其实也反应了目前人工智能发展的方向。 不过从长远来看&#xff0c;垂直领域大模型的发展会比通用大模型更具势头&#xff0c;一…

Java线程面试题

0. Java线程面试题 0.1 线程占用的内存 JDK1.4默认单个线程占用256KJDK1.5默认单个线程占用1M可以通过-Xss参数设定 0.2 为什么要使用线程池 手动创建线程池的缺点 不受风险控制&#xff1a;服务器CPU资源有限&#xff0c;如果每个人都显示手动创建线程&#xff0c;不知道哪…

Office远程代码执行漏洞(CVE-2017-11882)漏洞复现

Office远程代码执行漏洞&#xff08;CVE-2017-11882&#xff09;漏洞复现 1.漏洞原理2.在Kali平台查看漏洞利用文件3.登录目标靶机&#xff0c;打开FTP服务器4.登录Kali&#xff0c;利用FTP服务器上传payload-cale.doc文件5.登录目标靶机&#xff0c;触发doc文件&#xff0c;验…

虚函数,抽象基类

编译器处理虚函数的方法&#xff1a; 给每个对象添加一个隐藏成员&#xff0c;隐藏成员保存了一个指向函数地址的数组指针&#xff0c;数组被称为虚函数表&#xff0c;虚函数表存储了为类对象声明的虚函数的地址&#xff0c;比如基类包含一个指针&#xff0c;该指针指向基类中…

阿里巴巴变革,盒马“一马当先”

配图来自canva可画 随着数字经济成为当前经济社会发展的“新宠”&#xff0c;诸多数字企业也开始走上了发展的快车道。而作为国内互联网大厂的阿里巴巴集团&#xff0c;为了打造数字经济时代的商业、金融、物流、云计算和大数据等新一代商业基础设施&#xff0c;就于前不久启动…

Javawed第一章:Web前端的入门理论

目录 前言 一.wed &#x1f496;wed是什么&#xff1f; &#x1f496; wed的分类 二.HTML 和 CSS &#x1f496;HTML的介绍 HTML的标签 &#x1f496;CSS的介绍 常用基本标签 &#x1f496;实践 HTML结构标签特点 三.JavaScript &#x1f496;JavaScript的介绍 &…

SOLIDWORKS仿真数据清扫工具

我们来聊下SOLIDWORKS仿真数据清扫工具。与 SOLIDWORKS 软件一起安装的一个鲜为人知的工具是 Simulation Cleaning Utility。该实用工具可用于在 SOLIDWORKS 零件或装配文件中永远删除任何仿真数据&#xff0c;包括仿真设置和后处理信息。 SOLIDWORKS仿真数据清扫工具工具可以…

阻塞队列(消息队列)

1、阻塞队列 队列是一种先进先出的数据结构。而阻塞队列也是一种特殊的队列&#xff0c;也遵守”先进先出“的原则。 阻塞队列是一种线程安全的的数据结构&#xff0c;并且具有以下特性&#xff1a; 1、队列往进写元素是从队尾插入&#xff0c;队首取出 2、当插入元素的时候…

Python面向对象编程基础知识和示例代码

文章目录 对象&#xff08;Object&#xff09;示例代码一 类的成员方法&#xff08;Method&#xff09;示例代码二 类和对象&#xff08;Class and Object&#xff09;&#xff1a;示例代码三 构造方法&#xff08;Constructor&#xff09;&#xff1a;示例代码四 魔术方法&…

JUC简介

1、JUC介绍 JUC (java.util.concurrent)是在并发编程中使用的工具类&#xff0c;主要包括以下三个 &#xff08;1&#xff09;java.util.concurrent &#xff08;2&#xff09;java.util.concurrent.atomic 原子性&#xff1a;不可分割。Int i0; i, &#xff08;3&#xff09;…

MySQL——函数与约束的讲解

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 前言 本章将会讲解MySQL数据库的函数与约束的讲解。 一.函数 函数 是指一段可以直接被另一…

渗透测试面试题汇总

2023年快过去一半了&#xff0c;不知道小伙伴们有没有找到自己心仪的工作呀【doge】&#xff0c;本文总结了常见的安全岗位面试题&#xff0c;方便各位复习。祝各位事业顺利&#xff0c;财运亨通。在网络安全的道路上越走越远&#xff01; 所有的资料都整理成了PDF&#xff0c…

DCN v1阅读笔记

DCN v1即 Deformable Convolutional Networks。 视觉识别&#xff08;例如对象检测和语义分割&#xff09;中的一个关键挑战是如何适应物体尺度、姿态、视角和零件变形中的几何变化或模型几何变换。卷积神经网络&#xff08;CNN&#xff09;构建模块中为固定几何结构&#xff1…

神经网络基础

文章目录 一、神经网络基础1.得分函数 f(xi;W,b)1&#xff09;从输入到输出的映射2&#xff09;数学表示3&#xff09;计算方法4&#xff09;多组权重参数构成了决策边界 2.损失函数 L3.前向传播4.Softmax分类器 梯度下降2.反向传播 一、神经网络基础 回归任务&#xff1a;最终…

软件测试技能,JMeter压力测试教程,JDBC配置连接mysql数据库(十)

前言 使用jmeter压测接口的时候&#xff0c;有时候需要批量造数据&#xff0c;需使用jmeter连数据库造对应的测试数据 或者测试结束后&#xff0c;对测试的数据还原&#xff0c;删掉一些垃圾数据&#xff0c;都会用到连接数据库执行sql的操作 一、JDBC 连接配置 添加配置元…