js基于div丝滑实现贝塞尔曲线


Posted in Javascript onSeptember 23, 2022

引言

今天遇到朋友发来的一个ui图,询问我如何实现下图这样的效果【vue项目】,(听说是类似LED灯的展示效果),于是便有了今天的小demo,要实现一个类似下图的动效,上面的灯会一直重复滚动,但是这个并不是什么难点,主要在于如何实现这种平滑的曲线,日常我们的开发的div在我们的脑海中通常就是一个网格状,涉及到平滑曲线的往往是图表,于是我们需要找一个方案来完成这种布局(非真实ui图,是完成之后的效果)

js基于div丝滑实现贝塞尔曲线

分析

我们需要先简单分析一下这个ui,当我们拿到这个UI图的时候,脑海中的第一反应是,一个大的DIV中间套了很多的小的DIV,并且小的的上下位置出现了偏移,但是偏移多少目前我们不得而知,但是基础的布局方案已经完成。

第二步我们考虑球体的颜色,可以看到,轨道是一种颜色,需要一直移动的球体是另一种颜色,这个非常简单,我们定义两组数据,一组是轨道,一组是高亮的球,通过不段改变高亮的这组数据,即可响应式的完成灯的移动,第二点我们也解决了

第三点,初始的时候考虑的是y的坐标是0, 2, 4, 6, 8, 10 , 8, 6, 4 , 2 ..... ,但是很显然,这样的坐标出来的形状一定是一个折线图,而不是平滑的曲线,于是我们需要用到数学知识了:需要使用到圆的弧度的概念,在javascript中有两个方法**Math.sin()和Math.cos()**都是关于弧度的公式,关于这两个方法,我们下面再说。

实现

布局

实现这个的布局非常简单,外层一个大的div,内层很多小的span,通过flex一排即可到一排

<template>
  <div class="container">
    <div class="content">
      <span class="circle" v-for="(item,index) in list" :key="index"></span>
    </div>
  </div>
</template>

如何计算y的偏移量

这一步是我们比较重要的一步,我们有一个400px的容器,容器中放置了20个span,现在他们在一排,我们只需要给他动态绑定样式**transform: translateY(?px)**即可,重要的是我们如何计算这个的坐标,我们先来了解下两个方法的用处:

Math.sin() 和 Math.cos()

Math.sin(x)      x 的正玄值。返回值在 -1.0 到 1.0 之间;

Math.cos(x)    x 的余弦值。返回的是 -1.0 到 1.0 之间;

可以看到其分别是x点的正弦,这两个函数中的x指的是弧度而不是角度弧度的计算公式是:2π/360°

这里涉及到数学知识,我们先看看这张图

js基于div丝滑实现贝塞尔曲线

我们看我们关注的sin和cos

sin(∠A) = 对边比斜边(a / c)
cos(∠A) = 临边比斜边 (b / c)

可大致了解一下即可,当然,我们今天所需要使用的和这个关系不大,这里只是帮大家回顾一下高中知识(手动狗头)。

好了这里直接推荐一个在线网站,图形计算器可以直接在线调试各种曲线

我们看看基础的正弦余弦曲线

正弦曲线

js基于div丝滑实现贝塞尔曲线

余弦曲线

js基于div丝滑实现贝塞尔曲线

我们知道圆周率(π), 1π=180°2π=360°,就是一周,所以我们只需要截图(0-2π)一个周期的曲线即可,后续不管要什么曲线,都在这个上面进行变换即可,通过上面对比,发现正弦曲线的起始点是(0,0),比余弦的(0,1)更好计算,我们就直接用正弦吧,那么我们列出已知条件:

  • 在曲线中 y = cos(x)
  • 在曲线中,曲线的宽度是
  • 在曲线中,曲线的高度最高点到最低点是2
  • 在我们的需求中,总宽度是400px
  • 在我们需求中, 共有二十个圆圈,所以我们可以算出每个球的宽度平均是20px,所以坐标就是(index+1)*20
  • 现在我们知道了很多信息,我们就可以计算出更多信息了

计算更多信息

我们知道曲线的宽度和我们的物理实际宽度就可以得出宽度比: 400 / 2π

这个时候我们需要通过这个比例计算出物理的x坐标对应的曲线中的x坐标,那么 物理宽度/x坐标 = 2π/曲线中x坐标

/* 400 / x = 2π / y, 我们的x是已知的,等下自己可以拿,这样拿到了曲线中实际的x坐标 */
const z = 400 x / 400 * Math.PI*2

有个曲线中的对应x坐标,通过公式我们就可以拿到其曲线中实际y坐标了

/* 这样就拿到了曲线中的y坐标 */
y = Math.sin(z)

拿到了曲线中的y坐标,那么们又知道,曲线中的总高是2,通过xy的坐标对比,我们可以计算出我们所需的真实的y

/* 真实宽度400/曲线宽度2π = 真实高度y/曲线中的y 通过对比得到真实的y点 */
Y = Math.sin(z) * 400 / Math.PI * 2 / 2

然后通过这样的一个计算公式把这个y值赋值给我们的y点就可以得到这样的曲线

js基于div丝滑实现贝塞尔曲线

完善剩余

看起来有点意思了,这就是一个完整的2π,或者我们理解为就是曲线的一个周期,但是很明显曲线的度数不对,我们如何调整呢,回到刚刚的那个网站之中,我们要想曲线更加平滑,只需要对sin()除以/x即可,x最大线越平,我们到刚刚的网站去自己调试到自己理想的高度,

js基于div丝滑实现贝塞尔曲线

我们调试发现除以4就得到了差不多我们想要的曲线,所以我们只需要在上面的基础上/4就得到了我们真正想要的y。

js基于div丝滑实现贝塞尔曲线

此时我们的曲线就已经完成了,所以其实是不是就是我们的高中数学知识呢

完成跑马灯制作

前面的曲线画完,后面就已经不难了,我们只需要定义一段高亮的下标数组,我们写一个方法,创建一个自己想要高亮几个就生成0-x的数组

createActiveIndex(len = 6){
  return Array.from({length:len}, (v,k) => k)
},

然后在给span动态绑定一个背景颜色。当index属于高亮的时候就给高亮的颜色,不是则反之,然后我们写一个定时器一直修改这个高亮的数组即可,每次让其里面所有元素加1,就可以让他一直跑下去了,当然边界的时候我们需要对他进行归0

changeIndex(){
   this.activeIndex = this.activeIndex.map( item => item === this.list.length - 1 ? 0 : item + 1)
},

最后我们启动即可,就实现了我们开头想要的效果。

js基于div丝滑实现贝塞尔曲线

至此这个需求算是完成了,这只是一个小的场景通过这样的方式我们可以绘制出更多好玩的东西,你可以改变各种参数对齐进行调整修改,看看是不是你想要的效果

贝塞尔曲线

我们知道,前端的动画经常出现一个名词贝塞尔曲线,就是动画的执行过程,我们刚刚的曲线其实就是同理,如果此时我们需要去手动书写一个贝塞尔曲线我们应该怎么做呢,刚刚我们知道,我们容器的总宽度是400,曲线的周长是2π,比例就是400/2π,同理,当我们换算成时间的时候,假如动画是1秒。

那么我们需要60帧,一帧动画的时间就是1000/60=16.7ms,我们通过2π/60就知道我们每一帧动画在什么位置了,当我们手写贝塞尔曲线的时候,利用差不多的公式一样可以完成。

简单封装一下方法

看起来似乎很复杂,但是实际上我们所需要的其实只是利用真实的x点,拿到对应曲线求出我们y的坐标,所以我们需要的参数有,我们真实场景的总宽,总宽之中的个数,我们所需要的曲线的倍率,三个参数即可,我们尽量分开步骤写,这样你看会理解的更清楚

js中π就是Math.PI

function getCoordinate(width, count,  mag = 1){
      /* 通过总宽和个数计算出一个单个的宽 */
      const singleWidth = width / count
      /* 通过物理宽度/曲线周长计算出比率 */
      const ratio = 400 / Math.PI*2
      /* 上面实例代码我们是动态一次计算一个,而现在是方法,我们应该一次去拿到所有,所以我们返回一个数组对象记录xy */
      let result = new Array(count).fill({})
      /* 遍历总长度的dom个数,在数组中填充宽高 */
      result = result.map( (item,index) => {
          /* x的坐标 */
          const x = (index + 1) * singleWidth
          /* 定义变量z计算曲线中x的坐标 */
          const z = x / width * Math.PI*2
          /* 计算出真实的y的坐标 */
          let y = Math.sin(z) / 4  * 400 / Math.PI * 2 / 2    
          /* y还需要通过倍率改变曲线,得到最终我们想要的y */
          y = y / mag
          /* 写入数组对象中 */
          return {x, y}
      })
      return result;
    }

完整示例

style

.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #000;
}
.content{
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 400px;
  height: 50px;
}
.container .circle{
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background-color: #befbf7;
}

SCript

<template>
  <div class="container">
    <div class="content">
      <span class="circle" v-for="(item,index) in list" :key="index" :style="{transform: `translateY(${calcY(index)}px)`,backgroundColor: getCurrentBgColor(index)}"></span>
    </div>
     <div class="content" style="margin-top: 50px">
      <span class="circle" v-for="(item,index) in list" :key="index" :style="{transform: `translateY(${calcY(index)}px)`,backgroundColor: getRandomBgColor(index)}"></span>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      list: [], //定义总长度
      activeIndex: [], // 运动中的球的颜色
      interval: 300, //运动速度
      colors: { // 定义轨道颜色和高亮颜色
        active: '#2b88ff',
        basic: '#b6f3f7' 
      },
      cache: []
    };
  },
  methods: {
    init() {
      this.list = new Array(20).fill(0)
      this.start()
    },
    getCurrentBgColor(index){
      return this.activeIndex.includes(index) ? '#6e9cae' : this.getRandomColor()
    },
    getRandomBgColor(index){
      const color = this.activeIndex.includes(index) ? 'active' : 'basic'
      return this.colors[color]
    },
    start(){
      this.activeIndex = this.createActiveIndex()
      setInterval(() => this.changeIndex(), this.interval)
    },
    changeIndex(){
      this.activeIndex = this.activeIndex.map( item => item === this.list.length - 1 ? 0 : item + 1)
    },
    /* 生成需要动的球的个数 */
    createActiveIndex(len = 6){
      return Array.from({length:len}, (v,k) => k)
    },
    getRandomColor(){
        return `#${Math.floor(Math.random() * 0xffffff) .toString(16)}`;
    },
    getCoordinate(width, count,  mag = 1){
      /* 通过总宽和个数计算出一个单个的宽 */
      const singleWidth = width / count
      /* 通过物理宽度/曲线周长计算出比率 */
      const ratio = 400 / Math.PI*2
      /* 上面实例代码我们是动态一次计算一个,而现在是方法,我们应该一次去拿到所有,所以我们返回一个数组对象记录xy */
      let result = new Array(count).fill({})
      /* 遍历总长度的dom个数,在数组中填充宽高 */
      result = result.map( (item,index) => {
          /* x的坐标 */
          const x = (index + 1) * singleWidth
          /* 定义变量z计算曲线中x的坐标 */
          const z = x / width * Math.PI*2
          /* 计算出真实的y的坐标 */
          let y = Math.sin(z) / 4  * 400 / Math.PI * 2 / 2    
          /* y还需要通过倍率改变曲线,得到最终我们想要的y */
          y = y / mag
          /* 写入数组对象中 */
          return {x, y}
      })
      return result;
    } 
  },
  created(){
    this.cache = this.getCoordinate(400, 20, 1)
    this.init()
  },
  computed:{
    calcY(){
      return (index) => {
        /* 使用封装的方法计算 */
        // return this.cache[index].y
        const x = (index + 1) * 20
        const z = x / 400 * Math.PI*2
        const y = Math.sin(z) * 400 / Math.PI * 2 / 2 / 4
        return y
      }
    }
  }
};
</script>

以上就是js基于div丝滑实现贝塞尔曲线的详细内容,更多关于js div实现贝塞尔曲线的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
为javascript添加String.Format方法
Aug 11 Javascript
Javascript Math ceil()、floor()、round()三个函数的区别
Mar 09 Javascript
jquery 操作DOM的基本用法分享
Apr 05 Javascript
动态加载dtree.js树treeview(示例代码)
Dec 17 Javascript
javascript实现网页子页面遍历回调的方法(涉及 window.frames、递归函数、函数上下文)
Jul 27 Javascript
JavaScript字符串删除重复字符的方法
Dec 25 Javascript
AngularJS入门教程引导程序
Aug 18 Javascript
关于javascript获取内联样式与嵌入式样式的实例
Jun 01 Javascript
BootStrap模态框和select2合用时input无法获取焦点的解决方法
Sep 01 Javascript
layui-tree实现Ajax异步请求后动态添加节点的方法
Sep 23 Javascript
js实现简单的秒表
Jan 16 Javascript
js+for循环实现字符串自动转义的代码(把后面的字符替换前面的字符)
Dec 24 Javascript
TS 类型兼容教程示例详解
Sep 23 #Javascript
TS 类型收窄教程示例详解
Sep 23 #Javascript
JavaScript实现简单的音乐播放器
Aug 14 #Javascript
vue实现简易音乐播放器
Aug 14 #Vue.js
Vue3实现简易音乐播放器组件
Aug 14 #Vue.js
element tree树形组件回显数据问题解决
Aug 14 #Javascript
el-table-column 内容不自动换行的解决方法
Aug 14 #Vue.js
You might like
php实现mysql数据库备份类
2008/03/20 PHP
php 3行代码的分页算法(求起始页和结束页)
2009/10/21 PHP
WAMP环境中扩展oracle函数库(oci)
2015/06/26 PHP
php超快高效率统计大文件行数
2015/07/05 PHP
关于Laravel Route重定向的一个注意点
2017/01/16 PHP
mac os快速切换多个PHP版本的方法
2017/03/07 PHP
ThinkPHP3.2.3框架邮件发送功能图文实例详解
2019/04/23 PHP
JS 跳转页面延迟2种方法
2013/03/29 Javascript
javascript实现促销倒计时+fixed固定在底部
2013/09/18 Javascript
从数据库读取数据后将其输出成html标签的三种方法
2014/10/13 Javascript
js实现对table动态添加、删除和更新的方法
2015/02/10 Javascript
浅谈Javascript的静态属性和原型属性
2015/05/07 Javascript
Jquery简单实现GridView行高亮的方法
2015/06/15 Javascript
有关jquery与DOM节点操作方法和属性记录
2016/04/15 Javascript
jquery 判断是否支持Placeholder属性的方法
2017/02/07 Javascript
PHP自动加载autoload和命名空间的应用小结
2017/12/01 Javascript
NW.js 简介与使用方法
2018/02/01 Javascript
Node.js实现注册邮箱激活功能的方法示例
2018/03/23 Javascript
webpack的CSS加载器的使用
2018/09/11 Javascript
NodeJS实现同步的方法
2019/03/02 NodeJs
移动端底部导航固定配合vue-router实现组件切换功能
2019/06/13 Javascript
python中对list去重的多种方法
2014/09/18 Python
详解Python3中yield生成器的用法
2015/08/20 Python
详谈python read readline readlines的区别
2017/09/22 Python
Python坐标线性插值应用实现
2019/11/13 Python
python爬取招聘要求等信息实例
2020/11/20 Python
Hanky Panky官方网站:内衣和睡衣
2019/07/25 全球购物
类和结构的区别
2012/08/15 面试题
Linux管理员面试题 Linux admin interview questions
2016/07/08 面试题
商场促销活动方案
2014/02/08 职场文书
小学运动会入场式解说词
2014/02/18 职场文书
三月学雷锋月活动总结
2014/04/28 职场文书
电子商务求职信
2014/06/15 职场文书
优秀班组事迹材料
2014/12/24 职场文书
Python实现日志实时监测的示例详解
2022/04/06 Python
VUE递归树形实现多级列表
2022/07/15 Vue.js