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()
相关推荐
前端之虎陈随易9 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
一路向北he9 小时前
字节钢铁军团--“提供情境,而非控制”
java·开发语言·前端
kyriewen10 小时前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
前端一小卒10 小时前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
铁皮饭盒10 小时前
用 Bun.cron 定时 7 月 7 日,为啥? 看图1
javascript
大圣编程11 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang12 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆12 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜13 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
oscar99914 小时前
Codex 编程智能体入门指南
ai·chatgpt·codex