ChatGPT帮我写了个上拉加载

背景

事情是这样的,本人目前一直从事着移动端H5的研发工作,很多情况下都会涉及到列表页的上拉加载交互。公司内部有自研开源框架better-scroll,也有基于better-scrollCube-UIScroll组件的实现,但是这些SDK对于我当时的项目来说,均有一定的改造成本,我需要的只是一个可以简单实现页面上拉效果的动画,同时在用户松手的时候执行一个回调函数

实现效果

ChatGPT问答

ChatGPT是一个非常好用的AI助手,每一个编程人员都应该熟练掌握它。你所需要做的就是捋清思路,以产品经理的角度告诉他你的需求,循序渐进,从功能概括、到细节优化。

帮我用js实现用户滑动页面到底部后,拉扯页面上拉加载的函数

GPT回答:

要实现用户滑动页面到底部后的上拉加载函数,你可以监听scroll事件,检查滚动位置和文档高度,然后触发加载操作。以下是一个简单的实例:

javascript 复制代码
// 设置触发加载的阈值(距离底部多少像素开始加载)
const threshold = 100;
// 标记是否正在加载中,防止重复加载
let isLoading = false;
// 监听滚动事件
window.addEventListener('scroll', function() {
    // 文档总高度
    const documentHeight = document.documentElement.scrollHeight;
    // 当前滚动位置
    const scrollPosition = window.innerHeight + window.scrollY;
    // 判断是否滑动到底部
    if (scrollPosition >= documentHeight - threshold && !isLoading) {
        // 触发加载
        isLoading = true;
        console.log('触发加载数据的操作,比如向服务器请求新数据');
        // 模拟加载过程(在实际应用中,这里应该是异步请求数据的地方)
        setTimeout(function() {
            isLoading = false;
            console.log('加载完成');
        }, 2000); // 这里模拟加载过程,实际情况应根据异步请求的时间进行调整
    }
});

在这个示例中,当用户滑动到页面底部时,触发加载操作。注意,这只是一个简单的示例,实际应用中,你可能需要根据你的后端逻辑进行相应的异步加载操作。

代码很简单,只是帮我检测了用户是否滑动到底部,因此我需要让他知道我想要的是动画实现。

我想要的是这个拉扯动画

理解了,你想要在用户滑动到页面底部时,执行一些拉扯(pull-to-refresh)的动画,类似于一些应用中的上拉加载功能。以下是一个使用简单的HTML和JavaScript实现的例子:

此次对话的关键代码:

javascript 复制代码
function handleRelease() {
  const scrollPosition = window.scrollY;
  const threshold = 50;
  if (!isRefreshing && scrollPosition >= threshold) {
      // 触发刷新动作
      isRefreshing = true;
      refreshContainer.style.top = '0';
      refreshText.innerText = 'Refreshing...';
      refreshIcon.innerText = '🔄';
      // 模拟异步加载过程
      setTimeout(() => {
          isRefreshing = false;
          refreshContainer.style.top = '-60px';
          refreshText.innerText = 'Pull to refresh';
          refreshIcon.innerText = '🔄';
      }, 2000); // 这里模拟加载过程,实际情况应根据异步请求的时间进行调整
  }
}

这个例子包含了一个简单的上拉加载容器 refreshContainer,当用户上拉时,会出现下拉刷新的提示。你可以根据自己的需求进行更改和定制。在实际应用中,你可能需要根据异步加载的过程进行更复杂的处理。

我想要的是用户触底后,拉扯页面,页面会随用户的手势往上滑动,但是有阻尼感

理解了,你希望在用户触底后,拉扯页面时,页面会跟随用户的手势往上滑动,并且有阻尼感。以下是一个简单的实现,使用了 HTML、CSS 和 JavaScript。

此次对话的关键代码:

javascript 复制代码
function handleTouchStart(event) {
  if (content.scrollTop === content.scrollHeight - content.clientHeight) {
      isDragging = true;
      startY = event.touches[0].clientY;
  }
}

function handleTouchMove(event) {
  if (isDragging) {
      const deltaY = event.touches[0].clientY - startY;
      const translateY = Math.min(0, deltaY);
      content.style.transform = `translateY(${translateY}px)`;
  }
}

function handleTouchEnd() {
  if (isDragging) {
      isDragging = false;
      content.style.transform = 'translateY(0)';
      // 模拟异步加载过程
      loaderText.innerText = 'Loading...';
      loaderIcon.innerText = '🔄';
      setTimeout(() => {
          loaderText.innerText = 'Pull to load more';
          loaderIcon.innerText = '🔄';
      }, 2000); // 这里模拟加载过程,实际情况应根据异步请求的时间进行调整
  }
}

这个例子使用了 translateY 属性来实现页面的垂直平移,从而达到拉扯页面的效果。当用户触底并开始拉扯时,页面会根据手势进行垂直平移,并且有阻尼感。在实际应用中,你可以根据需求调整样式和动画效果。

在优化一下,用户拉扯的过程中,页面的滚动距离随用户滑动距离越来越小,阻尼越来越大

理解了,你想要在用户拉扯的过程中,页面的滚动距离随着用户滑动距离增加而减小,即增加阻尼效果。以下是经过调整的代码:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pull to Load</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
            height: 100vh;
            overflow: hidden;
            transition: transform 0.3s ease-out;
        }

        #content {
            background-color: #f0f0f0;
            height: 200vh;
        }

        #loader {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 60px;
            background-color: #f0f0f0;
        }
    </style>
</head>
<body>
    <div id="content">
        <!-- Your page content goes here -->
    </div>
    <div id="loader">
        <div id="loaderIcon">🔄</div>
        <div id="loaderText">Pull to load more</div>
    </div>
    <script>
        const content = document.getElementById('content');
        const loader = document.getElementById('loader');
        const loaderText = document.getElementById('loaderText');
        const loaderIcon = document.getElementById('loaderIcon');
        let isDragging = false;
        let startY = 0;
        let initialTranslateY = 0;
        function handleTouchStart(event) {
            if (content.scrollTop === content.scrollHeight - content.clientHeight) {
                isDragging = true;
                startY = event.touches[0].clientY;
                initialTranslateY = getTransformTranslateY(content);
            }
        }
        function handleTouchMove(event) {
            if (isDragging) {
                const deltaY = event.touches[0].clientY - startY;
                const translateY = Math.min(0, deltaY / 2 + initialTranslateY); // 调整除数来增加阻尼效果
                content.style.transform = `translateY(${translateY}px)`;
            }
        }
        function handleTouchEnd() {
            if (isDragging) {
                isDragging = false;
                // 模拟异步加载过程
                loaderText.innerText = 'Loading...';
                loaderIcon.innerText = '🔄';
                setTimeout(() => {
                    loaderText.innerText = 'Pull to load more';
                    loaderIcon.innerText = '🔄';
                }, 2000); // 这里模拟加载过程,实际情况应根据异步请求的时间进行调整

                // 添加过渡效果,平滑过渡到原始位置
                content.style.transition = 'transform 0.3s ease-out';
                content.style.transform = 'translateY(0)';
                // 在过渡结束后移除过渡属性,以避免影响后续的滚动
                content.addEventListener('transitionend', function transitionEnd() {
                    content.style.transition = '';
                    content.removeEventListener('transitionend', transitionEnd);
                });
            }
        }
        function getTransformTranslateY(element) {
            const transform = window.getComputedStyle(element).transform;
            const translateYMatch = transform.match(/matrix\(1, 0, 0, 1, 0, (-?\d+)\)/);
            return translateYMatch ? parseFloat(translateYMatch[1]) : 0;
        }
        content.addEventListener('touchstart', handleTouchStart);
        content.addEventListener('touchmove', handleTouchMove);
        content.addEventListener('touchend', handleTouchEnd);
    </script>
</body>
</html>

在这个版本中,我在 handleTouchMove 函数中调整了 deltaY 的计算,通过除以一个较大的数值,使得页面的滚动距离在用户滑动距离增加时逐渐减小,达到阻尼效果。你可以根据需要调整这个数值来改变阻尼效果的强弱。

代码整合

经过最后一步问答后,GPT给出的代码运行效果即为文章开头的实现,我们需要对代码进行以下优化:

  1. 将他给出的代码抽离为一个class模块儿
  2. 将动画实现所使用的transform改为position的变化(对子元素的fixed定位有影响,transform会改变屏幕视口

fixed 属性会创建新的层叠上下文。当元素祖先的 transformperspectivefilterbackdrop-filter 属性非 none 时,容器由视口改为该祖先。

最终代码:

typescript 复制代码
class PullLoad {
  hitY = 0
  startY = 0
  container: HTMLElement
  isFlick = false
  isDragging = false
  options = {
    bounce: true,
    dampingFactor: 4 // 阻尼系数
  }

  destory = () => { /* */ }
  onPullingUp = () => { /* */ }

  constructor(container: HTMLElement, onPullingUp?: () => void, options?: { dampingFactor?: number; bounce?: boolean }) {
    if (!container) {
      return
    }
    this.container = container
    container.style.position = 'relative'
    const handleTouchStart = this.handleTouchStart.bind(this)
    const handleTouchMove = this.handleTouchMove.bind(this)
    const handleTouchEnd = this.handleTouchEnd.bind(this)
    const initListener = () => {
      container.addEventListener('touchstart', handleTouchStart)
      container.addEventListener('touchmove', handleTouchMove)
      container.addEventListener('touchend', handleTouchEnd)
    }

    this.destory = () => {
      this.container.removeEventListener('touchstart', handleTouchStart)
      this.container.removeEventListener('touchmove', handleTouchMove)
      this.container.removeEventListener('touchend', handleTouchEnd)
    }

    initListener()
    Object.assign(this.options, options)
    if (onPullingUp) {
      this.onPullingUp = onPullingUp
    }
  }

  handleTouchStart (event: TouchEvent) {
    this.startY = event.touches[0].clientY
  }

  handleTouchMove (event: TouchEvent) {
    if (event.changedTouches[0].clientY > this.startY) {
      return
    }

    const contentEl = this.container
    const documentEl = document.documentElement
    const scrollTop = documentEl.scrollTop || window.pageYOffset || document.body.scrollTop // 兼容处理微信安卓版获取不到documentEl.scrollTop的问题
    const innerHeight = window.innerHeight
    const scrollHeight = documentEl.scrollHeight
    const distance = scrollHeight - (scrollTop + innerHeight)
    if (distance < 1) { // 部分安卓浏览器(qq浏览器)橡皮筋效果计算后的值大于0小于1,其余均小于或等于0
      if (!this.isDragging) {
        contentEl.style.transition = ''
        // 保证从碰撞开始的位置进行拉扯
        this.hitY = event.touches[0].clientY
      }
      const deltaY = event.touches[0].clientY - this.hitY
      const translateY = Math.min(0, deltaY / this.options.dampingFactor) // 调整除数来增加阻尼效果
      if (this.options.bounce) {
        contentEl.style.top = `${translateY}px`
      }
      this.isFlick = Math.abs(translateY) <= 10
      this.isDragging = true
    }
  }

  handleTouchEnd (event: TouchEvent, onTransitionend?: () => void) {
    if (this.isDragging) {
      this.isDragging = false
      if (!this.isFlick) {
        this.onPullingUp()
      }
      if (this.options.bounce) {
        const contentEl = this.container
        // 添加过渡效果,平滑过渡到原始位置
        contentEl.style.transition = 'top 0.3s ease-out'
        contentEl.style.top = '0'
        // 在过渡结束后移除过渡属性,以避免影响后续的滚动
        contentEl.addEventListener('transitionend', function transitionEnd () {
          contentEl.style.transition = ''
          contentEl.removeEventListener('transitionend', transitionEnd)
          // eslint-disable-next-line no-unused-expressions
          onTransitionend?.()
        })
      }
    }
  }
}

export default PullLoad

使用方法如下:

typescript 复制代码
import PullLoad from './pullLoad'

// 初始化
const el: HTMLDivElement = document.querySelector('#container')
const instance = new PullLoad(el, () => {
    console.log('callback,用户松手啦!')
})

// 组件卸载的时候
instance.destory()
相关推荐
海威的技术博客1 分钟前
JS中的原型与原型链
开发语言·javascript·原型模式
雪落满地香8 分钟前
前端:改变鼠标点击物体的颜色
前端
余生H40 分钟前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
outstanding木槿1 小时前
JS中for循环里的ajax请求不数据
前端·javascript·react.js·ajax
酥饼~1 小时前
html固定头和第一列简单例子
前端·javascript·html
一只不会编程的猫1 小时前
高德地图自定义折线矢量图形
前端·vue.js·vue
所以经济危机就是没有新技术拉动增长了1 小时前
二、javascript的进阶知识
开发语言·javascript·ecmascript
m0_748250931 小时前
html 通用错误页面
前端·html
来吧~1 小时前
vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)
前端·vue.js·音视频
Bubluu1 小时前
浏览器点击视频裁剪当前帧,然后粘贴到页面
开发语言·javascript·音视频