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

news2024/12/26 21:22:09

我们在日常前端开发时在动画的选择上基本都是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>

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

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

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

相关文章

【uni-app】创建自定义模板

1. 步骤 打开自定义模板文件夹 在此文件夹下创建模板文件&#xff08;注意后缀名&#xff09; 重新点击“新建页面” 即可看到新建的模板 2. 注意事项 创建的模板必须文件类型对应&#xff08;vue模板就创建*.vue文件, uvue模板就创建*.uvue文件&#xff09;

03哈希表算法/代码随想录

三、哈希表 有效的字母异位词 力扣242 这题是典型的哈希映射&#xff0c;只要将t存到哈希表中&#xff0c;key为t拆解的值&#xff0c;value为t中有过个key这样的值&#xff0c;然后在使用哈希表O&#xff08;1&#xff09;的时间复杂度判断 class Solution {public boolean …

下载安装COPT+如何在jupyter中使用(安装心得,windows,最新7.2版本)

目录 1.到杉树科技官网申请下载COPT 2.安装COPT&配置许可文件 3.在jupyter中使用COPT的python接口 最近看到一本和数学建模有关的新书&#xff1a;《数学建模与数学规划&#xff1a;方法、案例及编程实战》&#xff0c;作为数学建模老手&#xff0c;肯定要学习一下&…

【Linux】——操作系统-进程详解

大家好呀&#xff0c;我是残念&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流哦 本文由&#xff1a;残念ing原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&#xff0c;欢迎各…

Excel:vba实现批量插入图片批注

实现的效果&#xff1a;实现的代码如下&#xff1a; Sub InsertImageNamesAndPictures()Dim PicPath As StringDim PicName As StringDim PicFullPath As StringDim RowNum As IntegerDim Name As StringDim Comment As CommentDim folder As FileDialog 定义文件选择对话框 清…

HTML 语法规范——代码注释、缩进与格式、标签与属性、字符编码等

文章目录 一、代码注释1.1 使用注释的主要目的1.2 使用建议二、标签的使用2.1 开始标签和结束标签2.2 自闭合标签2.3 标签的嵌套2.4 标签的有效性三、属性四、缩进与格式4.1 一致的缩进4.2 元素单独占用一行4.3 嵌套元素的缩进4.4 避免冗长的行五、字符编码六、小结在开发 HTML…

闯关leetcode——242. Valid Anagram

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/valid-anagram/ 内容 Given two strings s and t, return true if t is an anagram of s, and false otherwise. Example 1: Input:s “anagram”, t “nagaram” Output:true Example 2: Inp…

无人机之远程指挥中心技术篇

一、核心功能 实时监控与控制&#xff1a; 通过高清视频流和其他传感器数据&#xff0c;指挥中心可以实时了解无人机的当前状态、位置和环境情况。操作人员可以在指挥中心对无人机进行精确的飞行控制&#xff0c;包括起飞、降落、悬停、移动等&#xff0c;确保无人机按照预定…

C++学习路线(数据库部分)二

类型 整形类型 整数类型是数据库中最基本的数据类型。标准SQL中支持INTEGER和SMALLINT这两种数据类型。MySQL数据库除了支持这两种类型以外&#xff0c;还扩展支持了TINYINT、MEDIUMINT和BIGINT。下表从不同整数类型的字节数、取值范围等方面进行对比。 类型名称后面的小括号…

秒杀优化(异步秒杀,基于redis-stream实现消息队列)

目录 秒杀优化一&#xff1a;异步秒杀1&#xff1a;思路2&#xff1a;实现 二&#xff1a;redis实现消息队列1&#xff1a;什么是消息队列2&#xff1a;基于list结构实现消息队列3&#xff1a;基于pubsub实现消息队列4&#xff1a;基于stream实现消息队列5&#xff1a;stream的…

机器视觉基础—双目相机

机器视觉基础—双目相机与立体视觉 双目相机概念与测量原理 我们多视几何的基础就在于是需要不同的相机拍摄的同一个物体的视场是由重合的区域的。通过下面的这种几何模型的目的是要得到估计物体的长度&#xff0c;或者说是离这个相机的距离。&#xff08;深度信息&#xff09…

Java使用apache.commons.io框架下的FileUtils类实现文件的写入、读取、复制、删除

Apache Commons IO 是 Apache 开源基金组织提供的一组有关IO&#xff08;Input/Output&#xff09;操作的小框架&#xff0c;它是 Apache Commons 项目的一部分&#xff0c;专注于提供简单易用的 API&#xff0c;用于处理输入和输出操作。Apache Commons IO 是一个功能强大的 J…

【论文解读】EdgeYOLO:一种边缘实时目标检测器(附论文地址)

论文地址&#xff1a;https://arxiv.org/pdf/2302.07483 这篇文章的标题是《EdgeYOLO: An Edge-Real-Time Object Detector》&#xff0c;由中国北京理工大学的Shihan Liu、Junlin Zha、Jian Sun、Zhuo Li和Gang Wang共同撰写。这篇论文提出了一个基于最新YOLO框架的高效、低复…

Redis 位图实现签到之长时间未签到预警

#目前通行系统项目中有一个新需求【通过对通行记录数据定时分析&#xff0c;查询出长时间没 有刷卡/刷脸通行的学生】 #一看到通行签到相关&#xff0c;就想到了redis的位图&#xff0c;理由也有很多帖子说明了&#xff0c;最大优点占用空间小。 一.redis命令行 SETBIT&#…

【Git】从 GitHub 仓库中移除误提交的 IntelliJ IDEA 配置文件夹 .idea 并将其添加到 .gitignore 文件中

问题描述 在使用Git进行版本控制时&#xff0c;不慎将.idea文件夹提交至GitHub仓库&#xff0c;即使后续在.gitignore文件中添加了.idea&#xff0c;但该文件夹仍在仓库中存在。 原因分析 .idea 是 IntelliJ IDEA 开发工具为项目创建的一个配置文件夹。IntelliJ IDEA 是一个广…

[Linux] 进程地址空间

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 好了&#xff0c;折腾…

Vue3 + Element Plus简单使用案例及【eslint】报错处理

本电脑Vue环境已安装正常使用 博主使用npm 包管理器安装 Element Plus.有问题评论区帮忙指正,感谢阅读. 在完成的过程中如果遇到eslint报错 Parsing error &#xff1a;Unexpected token { eslint 这个报错&#xff0c;也可以尝试第7部分报错处理解决。 目录 1.新建项目 2…

【云原生】Docker搭建开源翻译组件Deepl使用详解

目录 一、前言 二、微服务项目使用翻译组件的场景 2.1 多语言用户界面 2.2 业务逻辑中的翻译需求 2.3 满足实时通信的要求 2.4 内容管理系统 2.5 个性化推荐系统 2.6 日志和监控 三、开源类翻译组件解决方案 3.1 国内翻译组件方案汇总 3.1.1 百度翻译 3.1.2 腾讯翻…

DFA算法实现敏感词过滤

DFA算法实现敏感词过滤 需求&#xff1a;检测一段文本中是否含有敏感词。 比如检测一段文本中是否含有&#xff1a;“滚蛋”&#xff0c;“滚蛋吧你”&#xff0c;“有病”&#xff0c; 可使用的方法有&#xff1a; 遍历敏感词&#xff0c;判断文本中是否含有这个敏感词。 …

如何在Linux系统中使用Netcat进行网络调试

文章目录 Netcat简介安装Netcat在Debian/Ubuntu系统中安装在CentOS/RHEL系统中安装 Netcat基本命令Netcat基本用法示例1&#xff1a;监听端口示例2&#xff1a;连接到远程主机 Netcat选项-l选项-p选项-v选项 Netcat模式监听模式连接模式 Netcat排除和包含排除端口包含端口 Netc…