首页 > 其他分享 >滑动阻尼,惯性滚动列表,边界回弹,惯性回弹

滑动阻尼,惯性滚动列表,边界回弹,惯性回弹

时间:2024-10-17 15:00:33浏览次数:8  
标签:回弹 const 惯性 transform 阻尼 content return data Math

https://juejin.cn/post/7426280686695759882

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Inertia Scrolling with Bounce</title>
  <style>
    #container {
      width: 300px;
      height: 500px;
      overflow: hidden;
      border: 1px solid black;
      position: relative;
      box-sizing: border-box;
    }
    .inertia-content {
      min-height: 1000px;
      background: linear-gradient(to bottom, #ffcc00, #ff6600);
      display: grid;
      grid-gap: 10px;
      grid-template-columns: repeat(2, 1fr);
      justify-items: center;
      /* overflow-x: hidden; */
    }
    .item {
      height: 100px;
      width: 100px;
      background: white;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script>
    class Inertia {
      options = {
        max: 20
      };
      data = {
        container: null,
        content: null,
        containerHeight: 0,
        contentHeight: 0,
        scrollableHeight: 0,
        maxDistance: () => this.data.containerHeight * 0.9,
        isDragging: false,
        startY: 0,
        scrollY: 0,
        velocity: 0,
        lastMoveY: 0,
        lastTimestamp: 0,
        animationFrame: null,
        direction: 0,
      };
      static utils = {
        getTranslateY(element) {
          const style = window.getComputedStyle(element);
          const transform = style.transform || style.webkitTransform || style.mozTransform;

          if (transform && transform !== 'none') {
            // transform值通常是 "matrix(a, b, c, d, e, f)",其中e和f是translateX和translateY的值
            const match = transform.match(/matrix\(([^,]+),[^,]+,[^,]+,([^,]+),([^,]+),([^,]+)\)/);
            if (match) {
              return parseFloat(match[4]);
            }
          }
          return 0; // 如果没有translateY值,默认返回0
        },
        easeOutQuad(x) {
          return 1 - (1 - x) * (1 - x);
        },
        easeInOutCubic(x) {
          return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
        },
        easeOutQuint(x) {
          return 1 - Math.pow(1 - x, 5);
        },
        iosEase(x) {
          return Math.sqrt(1 - Math.pow(1 - 2*x, 2));
        },
        deci(number) {
          return Math.round(number * 10) / 10
        }
      };
      constructor(container, options) {
        this.data.container = document.querySelector(container);
        Object.assign(this.options, options);
        this.init();
      }
      async init() {
        this.createdInertiaContentEl();
        await this.renderContent();
        this.bindEvents();
      }
      createdInertiaContentEl() {
        this.data.container.insertAdjacentHTML('afterbegin', `<div class="inertia-content"></div>`);
        this.data.content = container.querySelector('.inertia-content');
      }
      async renderContent() {
        const { container, content } = this.data;
        const data = await this.options.rendered();
        container.querySelector('.inertia-content').innerHTML = data;
        this.data.containerHeight = container.offsetHeight;
        this.data.contentHeight = content.offsetHeight;
        this.data.scrollableHeight = this.data.contentHeight - this.data.containerHeight;
      }
      bindEvents() {
        container.addEventListener('touchstart', (e) => {
          e.preventDefault();
          const { content, animationFrame } = this.data;
          const touch = e.touches[0];
          Object.assign(this.data, {
            isDragging: true,
            startY: touch.clientY,
            scrollY: Inertia.utils.getTranslateY(content),
            velocity: 0,
            lastMoveY: touch.clientY,
            lastTimestamp: e.timeStamp,
          });
          cancelAnimationFrame(animationFrame);
        }, { passive: false });

        document.addEventListener('touchmove', (e) => {
          e.preventDefault();
          let { isDragging, content, startY, maxDistance, direction, lastTimestamp, lastMoveY, scrollY, scrollableHeight } = this.data;
          if (!isDragging) return;
          const { deci, easeOutQuad } = Inertia.utils;
          const touch = e.touches[0];
          // 偏移量
          const dy = touch.clientY - startY;
          // 实时滚动距离
          const y = scrollY + dy;
          // 1 向下滚动, -1 向上滚动
          direction = this.data.direction = Math.abs(dy)/dy;
          // 当接触到边界时,增加阻尼感 
          if(y > 0 || (y < -scrollableHeight && direction == -1)) {
            //下拉置顶或者上拉置底时,滑动距离
            const disY = direction == 1 ? Math.abs(dy) : Math.abs(y) - scrollableHeight;
            // 0.2是阻尼系数
            const realDisY = direction * easeOutQuad(disY * 0.1 / maxDistance()) * maxDistance();
            // 从哪里开始,会有阻尼感的距离滑动
            const originY = direction == 1 ? 0 : -scrollableHeight;
            content.style.transform = `translateY(${ originY + realDisY }px)`;
          }else {
            content.style.transform = `translateY(${this.data.scrollY + dy}px)`;
          }
          const now = e.timeStamp;
          const dt = now - lastTimestamp;
          this.data.velocity = (touch.clientY - lastMoveY) / dt * 1000;
          this.data.lastMoveY = touch.clientY;
          this.data.lastTimestamp = now;
        }, { passive: false });

        document.addEventListener('touchend', (e) => {
          e.preventDefault();
          const { lastTimestamp, content, isDragging } = this.data;
          const { max } = this.options;
          const { getTranslateY, easeInOutCubic } = Inertia.utils;
          if (!isDragging) {return}
          this.data.isDragging = false;
          const y = this.data.scrollY = getTranslateY(content);

          // 下拉距离小于max 或者 停止时间超过300ms 或者 速度小于60 ,则自动回弹到顶部
          // if((y > 0 && y < max) || e.timeStamp - lastTimestamp >= 300 || Math.abs(this.data.velocity) < 1) {
          //   y > 0 && this.transition(400, easeInOutCubic, (value) => {
          //     content.style.transform = `translateY(${ y - y * value }px)`;
          //   })
          //   return
          // }
          console.log('开启惯性滚动')
          this.animateInertia();
        }, { passive: false })


      }
      animateInertia() {
        const { getTranslateY, deci, easeInOutCubic, iosEase } = Inertia.utils;
        const self = this;
        const { content, scrollableHeight } = this.data;
        const { max } = this.options;
        function step() {
          //速度小于60就停止动画
          if(Math.abs(self.data.velocity) < 1) { 
            self.data.scrollY = getTranslateY(content)
            return
          }
          // 每一帧移动的像素数
          const deltaY = deci(self.data.velocity / 60);
          let y = getTranslateY(content);
          // 顶部回弹
          if(y > max) {
            self.transition(400, easeInOutCubic, (value) => {
              content.style.transform = `translateY(${ y - y * value }px)`;
            })
            return;
          }
          // 底部回弹
          if(y < 0 && Math.abs(y) - scrollableHeight > max) {
            y = Math.abs(y) - scrollableHeight;
            self.transition(500, easeInOutCubic, (value) => {
              content.style.transform = `translateY(-${ scrollableHeight + (y - y * value) }px)`;
            })
            return;
          }
          content.style.transform = `translateY(${ y + deltaY }px)`;
          self.data.velocity *= (1- 0.03);
          self.data.animationFrame = requestAnimationFrame(step);
        }
        requestAnimationFrame(step);
      }
      transition(duration = 500, easeFunction, framer) {
        let startTime = null;
        function step() {
          let time = Date.now();
          if (!startTime) startTime = time;
          let progress = (time - startTime) / duration;
          if (progress > 1) progress = 1;
          let value = easeFunction(progress);

          framer(value);

          if (progress < 1) {
            requestAnimationFrame(step);
          }
        }
        requestAnimationFrame(step);
      }

    }

    // 使用
    new Inertia('#container', {
      rendered() {
        return new Promise((resolve) => {
          let c = ``;
          for(let i = 0; i < 1000; i++) {
            c += `<div class="item" style="${ i < 2 && 'margin-top:10px' }">${i+1}</div>`;
          }
          resolve(c);
        })
      }
    });

  </script>
</body>
</html>

 

标签:回弹,const,惯性,transform,阻尼,content,return,data,Math
From: https://www.cnblogs.com/littleboyck/p/18472336

相关文章

  • Xsens MVN Analyze 惯性动作捕捉系统/人形机器人配套系统
    优化用于研发,运动科学,人体工程学和康复,人形机器人等Movella的Xsens动作捕捉解决方案,包含专有的MVNAnalyze软件。MVNAnalyze是一种基于惯性传感器,生物力学模型和传感器融合算法的全身人体测量系统。简单易用,设置时间短,即时验证数据输出,且使用不受环境因素影响。可在任何地......
  • 2024年抗震阻尼器行业现状与前景分析-聚亿信息咨询
    【出版机构】:聚亿信息咨询 (广东) 有限公司聚亿信息咨询(Market Monitor Global)调研机构最新发布了【抗震阻尼器市场调研报告,全球行业规模展望2024-2030】。本市场调研报告为读者提供专业且深入的产品销量、收入、价格、增长率、市场占有规模及竞争对手等数据分析,包含分析过去......
  • 粒子群算法中对于惯性权重的改进
    惯性权重w体现的是粒子继承先前的速度的能力,Shi,Y最先将惯性权重w引入到粒子群算法中,并分析指出一个较大的惯性权值有利于全局搜索,而一个较小的权值则更利于局部搜索。因此,在迭代适应度的同时对惯性权重进行迭代有利于帮助我们寻找最优解目录一、线性递减惯性权重1.迭代思想2.迭......
  • IMU惯性测量模块在ROS环境下的应用示例
    Ubuntu版本:20.04;ROS环境:noetic;IMU型号:亚博10轴IMU惯导模块目录一.ROS环境配置1、在终端运行对应的命令 2、安装ROS串口驱动二、IMU软件包使用1、新建、编译工作空间 2、绑定IMU端口3、修改参数配置 三、运行可视化界面 1、运行launch文件2、可能遇到的问题3、......
  • 数字样机:惯性导航系统控制单元仿真
    01.简介惯性导航系统 (INS,InertialNavigationSystem)基于惯性原理建立,而惯性是物体自身的固有属性,因此其工作时既不依赖于外部信息,也不向外部辐射能量,优于卫星导航与无线电导航,是一种具备隐蔽性、自主性的导航系统,被广泛应用于航空航天、无人机、智能交通等各类领域中,是复杂......
  • 【文献阅读】 PVDF &阻尼&有限元建模
    1.压电Damper原理PiezoelectricCompositeMaterials-ScienceDirect当振动传递到压电材料时,振动能量通过压电效应转化为电能,产生交流电压。所产生的电能\(U_E\)可以用机电耦合系数k和机械能\(U_m\)表示:\[U_\mathrm{E}=U_\mathrm{M}\timesk^2\]电路处于开路或者短路......
  • 【逆运动学2】damped least squares method阻尼最小二乘法
    逆运动学 逆运动学,就是从操作空间的endeffectorpositionandorientation,求关节空间的jointposition的问题。在之前的文章,我们简单提到求逆运动学解的解析解法和优化解法,详细讲解了用逆瞬时(或说微分)运动学即雅可比矩阵法迭代求解逆运动学的方法。这篇文章我们继续讲雅可比矩......
  • 惯性导航
     加速度计: 胡可定律:F=-kx;牛二定律:F=ma; 假定上图盒子不在任何力场中,盒子突然朝某个方向的移动,都会转换为球对盒子内部某些面的压力。假定盒子突然向左移动(1g=9.8m/s^2),球就会撞击x-墙,并对墙面产生压力(-1gm的力)。加速度本身的方向和加速度计检测到的力......
  • 垃圾桶阻尼器:小部件,大作用
    在我们日常生活中,垃圾桶是再常见不过的物品,但您是否留意过垃圾桶上那个看似不起眼的阻尼器?它虽小,在垃圾桶的使用体验中发挥着重要作用。阻尼器,这个略显专业的名词,其实就是一种以提供运动阻力,耗减运动能量的装置。当应用于垃圾桶时,它能有效地控制垃圾桶盖的开合速度,使其缓缓落下......
  • 240720-模态应变法计算阻尼-论文阅读
    技术报告:NASA-ComputationalSimulationofDampinginCompositeStructures摘要提出了一种复合材料结构被动阻尼预测的计算方法。该方法综合了微观力学、层压理论和结构阻尼理论,建立了多级阻尼模型。采用有限元离散化方法对结构层面的阻尼进行了模拟。论文中将这个方法应......