用插值公式实现滚动进度条动画效果

我们在日常前端开发时在动画的选择上基本都是css,通过css的animation即可满足大部分的开发场景,如果遇到了特殊而比较不容易实现的效果就会考虑到用js来实现,而本次的主题,就是围绕用js来做一个比较不常见的特殊动画效果。

假设我们要做一个进度条控制方块移动的动画,进度条开始方块在左边,进度条结束方块在右边。
是不是大脑里已经有了方案,看起来是一个容易实现的需求。
大致的思路:通过监听滚动条,滚动条0方块的left为0,滚动条100%方块的left100%。
当我们完成了这番代码后得出了以下的效果:
在这里插入图片描述
看起来是符合了自己的预期,但哪里感觉不太对。

感觉上这个效果还不够灵动?缺少某种缓动的动画效果。

在这一步,是卡了我非常久的地方,经过一番漫长的探索,终于发现了其中的奥秘。

我们看看带有缓动效果后的样子:
在这里插入图片描述
原来的方案速率是一致的,而通过调整后,方块移动的速率就不一样了。

那么这里的重点就在于一个公式:插值公式。
我们先看定义:
插值公式是数学中用于在离散数据的基础上补插连续函数的方法,使得这条连续曲线通过全部给定的离散数据点。插值是离散函数逼近的重要方法,通过函数在有限个点处的取值状况,估算出函数在其他点处的近似值。插值公式广泛应用于填充图像变换时像素之间的空隙,以及在计算力学等领域。

就当前这个动画来说,插值能在动画执行的过程中不断的计算下一次要移动的速率。
在这里插入图片描述
这两个动画有一个明显区别,当滚动条停下时,无插值的动画会立刻停下,而有插值的动画会在滚动条停下后依旧有缓动的效果再逐渐停下,从而形成一种动画感。

接下来是代码的实现过程:

<!--首页-->
<template>
  <div id="home" style="height:2000px">
    <div class="squar" ref="squars">方块</div>
  </div>
</template>
<script setup>
import {ref} from "vue";


//动画render
let prg = 0;
let lastPrg = 0;//上一次滚动条进度
let lerpValue = 0.05;

const init = ()=>{
    // 初始化函数
    prg = document.documentElement.scrollTop || document.body.scrollTop;
    lastPrg = document.documentElement.scrollTop || document.body.scrollTop;
}
//动画数据动画
const SetAnimData = (prg)=>{
    squars.value.style.left = prg+'px'
}

//插值函数
//x:当前位置 y:目标位置 t:插值参数百分比
const lerp = (x, y, t) =>{
    return (1 - t) * x + t * y;
}


let animateFrameId = '';
const animate = ()=>{
    animateFrameId = requestAnimationFrame(animate);
        prg = document.documentElement.scrollTop || document.body.scrollTop;
        if(prg>1000)prg=1000;//如果当前进度条大于整组动画进度最大值
        //如果滑动值比上次滑动值 大于lerpValue
        if (Math.abs(prg - lastPrg) > lerpValue)
        {
            //进行插值运算
            lastPrg = prg//lerp(lastPrg, prg, lerpValue);
            //执行动画数据动画
            SetAnimData(lastPrg);
        }
        // 小于插值 直接进行赋值,否则会无限的插值
        else
        {
            //直接赋值
            lastPrg = prg;
            //执行动画数据动画
            SetAnimData(lastPrg);
        }
}
let container = null
const squars = ref(null)
onMounted(()=>{
    container = document.getElementById("home");
    if(process.client && container.clientHeight){
      console.log(squars.value)
        init();
        animate();
    }
})

</script>

<style scoped lang="less">
.squar{
  width: 100px;
  height: 100px;
  background: red;
  position: fixed;
  top: 0;
  left: 0;
}
</style>

接下来是一个思考题,现在我们能做到的是滚动条从0-100% 方块从左到右的移动。
那么假设我们希望0-50%方块从左到右移动 50%-100%从上到下移动,这样该怎么做呢?
在实际的场景中,我们往往会需要动画有好几个阶段不同的形式进行插值动画,(让一组从左至右,下一组从上至下)。
如下图案例:(先上往下,再左到右)
请添加图片描述
像这个多组动画的形式,我采用的是for循环,循环多组动画,每组动画设置起始的进度和结束的进度条以及起始的进度值和结束的进度值:

//动画数据动画
let animates = {
                // 在0-1200进度条值的范围内 元素会从左到右0-1000的移动。
                'left':[
                    {
                        beginPrg:0,//起始进度条值
                        endPrg:1200,//结束进度条值
                        begin:0,//起始进度条 元素的起始值
                        end:1000,//结束进度条 元素的结束值
                    },
                ],
                'top':[
                    {
                        beginPrg:1200,
                        endPrg:2400,
                        begin:0,
                        end:1000,
                    },
                ],
            }
const SetAnimData = (prg)=>{
    // squars.value.style.left = prg+'px'

    for(let i in animates){
      for(let j in animates[i]){
        let animateOne = animates[i][j];
        console.log(animateOne,'animaO')
        //如果滚动条值在 起始滚动条和结束滚动条范围内
        // console.log(animateOne,'sssssss',prg,animateOne[0].beginPrg,)
        var prgValue = clamp((prg - animateOne.beginPrg) / (animateOne.endPrg - animateOne.beginPrg), 0, 1);

                              if ((1 - prgValue) <= 0.01){
                                  prgValue = 1;
                              }
                              if ((prgValue) <= 0.01){
                                  prgValue = 0;
                              }
                              
        let curValue= lerp(animateOne.begin, animateOne.end, prgValue);//通过插值计算当前模型要变化的值
        if (prg >= animateOne.beginPrg && prg < animateOne.endPrg)
        {
          squars.value.style[i] = curValue+'px'
        }
      }
    }
}

这样就能做出多组不同形式的动画效果了。
在这里插入图片描述

再往后就是多个元素同时的移动,以及细节方面的优化,考虑到篇幅会过长,这里就不再继续延申下去了。

完整的代码实现过程:

<!--首页-->
<template>
  <div id="home" style="height:2000px">
    <div class="squar" ref="squars">方块</div>
  </div>
</template>
<script setup>
import {ref} from "vue";

import {clamp} from 'lodash-es'

//动画render
let prg = 0;
let lastPrg = 0;//上一次滚动条进度
let lerpValue = 0.05;

const init = ()=>{
    // 初始化函数
    prg = document.documentElement.scrollTop || document.body.scrollTop;
    lastPrg = document.documentElement.scrollTop || document.body.scrollTop;
}
//动画数据动画
let animates = {
                // 在0-1200进度条值的范围内 元素会从左到右0-1000的移动。
                'left':[
                    {
                        beginPrg:0,//起始进度条值
                        endPrg:1200,//结束进度条值
                        begin:0,//起始进度条 元素的起始值
                        end:1000,//结束进度条 元素的结束值
                    },
                ],
                'top':[
                    {
                        beginPrg:1200,
                        endPrg:2400,
                        begin:0,
                        end:1000,
                    },
                ],
            }
const SetAnimData = (prg)=>{
    // squars.value.style.left = prg+'px'

    for(let i in animates){
      for(let j in animates[i]){
        let animateOne = animates[i][j];
        console.log(animateOne,'animaO')
        //如果滚动条值在 起始滚动条和结束滚动条范围内
        // console.log(animateOne,'sssssss',prg,animateOne[0].beginPrg,)
        var prgValue = clamp((prg - animateOne.beginPrg) / (animateOne.endPrg - animateOne.beginPrg), 0, 1);

                              if ((1 - prgValue) <= 0.01){
                                  prgValue = 1;
                              }
                              if ((prgValue) <= 0.01){
                                  prgValue = 0;
                              }
                              
        let curValue= lerp(animateOne.begin, animateOne.end, prgValue);//通过插值计算当前模型要变化的值
        if (prg >= animateOne.beginPrg && prg < animateOne.endPrg)
        {
          squars.value.style[i] = curValue+'px'
        }
      }
    }
}

//插值函数
//x:当前位置 y:目标位置 t:插值参数百分比
const lerp = (x, y, t) =>{
    return (1 - t) * x + t * y;
}


let animateFrameId = '';
const animate = ()=>{
    animateFrameId = requestAnimationFrame(animate);
        prg = document.documentElement.scrollTop || document.body.scrollTop;
        if(prg>2400)prg=2400;//如果当前进度条大于整组动画进度最大值
        //如果滑动值比上次滑动值 大于lerpValue
        if (Math.abs(prg - lastPrg) > lerpValue)
        {
            //进行插值运算
            lastPrg = lerp(lastPrg, prg, lerpValue);
            //执行动画数据动画
            SetAnimData(lastPrg);
        }
        // 小于插值 直接进行赋值,否则会无限的插值
        else
        {
            //直接赋值
            lastPrg = prg;
            //执行动画数据动画
            SetAnimData(lastPrg);
        }
}
let container = null
const squars = ref(null)
onMounted(()=>{
    container = document.getElementById("home");
    if(process.client && container.clientHeight){
      console.log(squars.value)
        init();
        animate();
    }
})

</script>

<style scoped lang="less">
.squar{
  width: 100px;
  height: 100px;
  background: red;
  position: fixed;
  top: 0;
  left: 0;
}
</style>

以上就是本次的分享,感谢观看!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【1个月速成Java】基于Android平台开发个人记账app学习日记——第4天,注册登录逻辑代码

24.11.03 1.输入手机号跳转功能 第一个要设计的功能是&#xff0c;输入手机号以后跳转到另一个页面&#xff0c;输入获取得到的验证码页面。先拿这个功能练练手。 首先看一下此时的完整项目结构&#xff1a; 主要是添加了2个活动类和对应的界面&#xff0c;下面看详细的代码…

ubuntu【桌面】 配置NAT模式固定IP

DHCP分配导致虚拟机IP老变&#xff0c;SSH老要重新配置&#xff0c;设成静态方便些 一、设NAT模式 1、设为NAT模式 2、看模式对应的虚拟网卡 - VMnet8 3、共享主机网卡网络到虚拟网卡 - VMnet8 二、为虚拟网卡设置静态IP 记住这个IP 三、设置ubuntu固定IP 1、关闭DHCP并…

数智驱动,纷享销客助力万东医疗实现精细化管理

数字化浪潮正在席卷整个医疗影像行业&#xff0c;数字化工具对疾病诊疗效率和诊疗质量的提升也有目共睹。北京万东医疗科技股份有限公司&#xff08;以下简称“万东医疗”&#xff0c;股票代码 600055&#xff09;成立于1955年&#xff0c;1997 年在上海证交所上市&#xff0c;…

项目模块十四:HttpRequest模块

一、项目设计思路 存储HTTP请求要素&#xff0c;提供简单接口 二、成员变量 全部公有 string _method; // 请求方法 string _path; // 资源路径 string _version; // 协议版本 string _body; // 请求正文 smatch _matches; // 资源路径正则提取 …

[HNCTF 2022 Week1]calc_jail_beginner_level3(JAIL)

开启靶场&#xff0c;打开链接&#xff0c;下载附件&#xff1a; 嗯&#xff0c;直接显示“Oh hacker!”&#xff0c;有点懵&#xff0c;先看看下载的附件&#xff08;server.py&#xff09;&#xff1a; 得到信息如下&#xff1a; 用户输入的表达式长度被限制在7个字符以内。…

最短路的求解

实验类型&#xff1a;◆验证性实验 ◇综合性实验 ◇设计性实验 实验目的&#xff1a;学会使用Matlab求解最短路。 实验内容&#xff1a;1.熟练运用Floyd算法&#xff1b;2. 熟练运用Dijkstra算法&#xff1b;3.利用Matlab编程实现最短路的计算。 例1&#xff1a;已知无向图…

目前最新最好用 NET 混淆工具 .NET Reactor V6.9.8

目前最新最好用 NET 混淆工具 .NET Reactor V6.9.8 1、.NET Reactor V6.9.8 功能简介2、官方下载 1、.NET Reactor V6.9.8 功能简介 业界领先的源代码保护 .NET Reactor通过多种方法来防止反编译&#xff0c;这些方法会将 .NET 程序集转换为任何现有工具都无法反编译的进程。…

为啥学习数据结构和算法

基础知识就像是一座大楼的地基&#xff0c;它决定了我们的技术高度。而要想快速做出点事情&#xff0c;前提条件一定是基础能力过硬&#xff0c;“内功”要到位。 想要通关大厂面试&#xff0c;千万别让数据结构和算法拖了后腿 我们学任何知识都是为了“用”的&#xff0c;是为…

Ubuntu 24.04上启用 root 用户通过 SSH 和图形界面进行登录

一、启用 root 用户的密码登录 设置 root 用户密码&#xff1a; 在终端中输入以下命令为 root 用户设置一个密码&#xff1a; testtest-virtual-machine:~$ sudo passwd root [sudo] test 的密码&#xff1a; 新的密码&#xff1a; 无效的密码&#xff1a; 密码是一个回文…

卡尔曼滤波器-Kalmen Filter-1

卡尔曼滤波器是一种最优递归数据处理算法&#xff0c;它更像是一种观测器&#xff0c;而不是一般意义上的滤波器。卡曼滤波器的应用非常广泛&#xff0c;尤其是在导航当中。它的广泛应用是因为我们生活的世界中存在着大量的不确定性&#xff0c;当我们去描述一个系统的时候&…

C++ | Leetcode C++题解之第519题随机翻转矩阵

题目&#xff1a; 题解&#xff1a; class Solution { public:Solution(int m, int n) {this->m m;this->n n;this->total m * n;srand(time(nullptr));}vector<int> flip() {int x rand() % total;vector<int> ans;total--; // 查找位置 x 对应的…

电子电气架构 --- 车载诊断的快速入门

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

【云备份项目】json以及jsoncpp库的使用

目录 1.JSON 2.什么是 JSON&#xff1f; 3.JSON 发展史 4.为什么要使用 JSON&#xff1f; 5.JSON 的不足 6.JSON 应该如何存储&#xff1f; 7.什么时候会使用 JSON 7.1.定义接口 7.2.序列化 7.3.生成 Token 7.4.配置文件 8.JSON的语法规则 8.1.对象和数组 8.2.JS…

DNS服务部署

DNS服务部署 1.要求 1.搭建dns服务器能够对自定义的正向或者反向域完成数据解析查询。 2.配置从DNS服务器&#xff0c;对主dns服务器进行数据备份。 2.配置 主服务器&#xff1a; 1.安装BIND [rootlocalhost xzy]# sudo dnf install bind bind-utils2.配置正向区域 [roo…

Python酷库之旅-第三方库Pandas(191)

目录 一、用法精讲 886、pandas.Index.repeat方法 886-1、语法 886-2、参数 886-3、功能 886-4、返回值 886-5、说明 886-6、用法 886-6-1、数据准备 886-6-2、代码示例 886-6-3、结果输出 887、pandas.Index.where方法 887-1、语法 887-2、参数 887-3、功能 8…

【C++】类和对象(十二):实现日期类

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的实现日期类&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1 /!/>/</>/<运算符重载2 /-//-运算符重载(A) 先写&#xff0c;再通过写(B…

免费送源码:Java+Springboot+MySQL Springboot酒店客房管理系统的设计与实现 计算机毕业设计原创定制

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对酒店客房管理等问题&#xff0c;对酒店客房…

无线配置实验

配置 sw1 [sw1]vlan batch 10 20 100 [sw1-GigabitEthernet0/0/1]port link-type trunk [sw1-GigabitEthernet0/0/1]port trunk allow-pass vlan 10 20 100 [sw1-GigabitEthernet0/0/1]port trunk pvid vlan 100 接口g124一样的配置 [sw1-GigabitEthernet0/0/3]port link-type…

在基于AWS EC2的云端k8s环境中 搭建开发基础设施

中间件下载使用helm,这里部署的都是单机版的 aws-ebs-storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata:name: aws-ebs-storageclass provisioner: kubernetes.io/aws-ebs parameters:type: gp2 # 选择合适的 EBS 类型&#xff0c;如 gp2、io1…

旋转位置编码

1. Transformer为什么需要位置编码 因为 transformer 结构本身是和位置编码无关的&#xff1a; Y T ( X ) F ( A ( X ) ) Y\Tau(X)F(A(X)) YT(X)F(A(X))&#xff0c;其中 A ( ) A() A() 是 attention 变换&#xff0c;只进行了矩阵变换&#xff0c;跟位置无关&#xff0c; …