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 相关文章推荐
JS中==与===操作符的比较
Mar 21 Javascript
JS getStyle获取最终样式函数代码
Apr 01 Javascript
获取中文字符串的实际长度代码
Jun 05 Javascript
js实现透明度渐变效果的方法
Apr 10 Javascript
Javascript之Math对象详解
Jun 07 Javascript
HTML5 canvas 9绘制图片实例详解
Sep 06 Javascript
JS实现页面打印功能
Mar 16 Javascript
jQuery插件FusionWidgets实现的Cylinder图效果示例【附demo源码】
Mar 23 jQuery
js实现移动端编辑添加地址【模仿京东】
Apr 28 Javascript
angular基于ng-alain定义自己的select组件示例
Feb 23 Javascript
使用Angular CLI进行Build(构建)和Serve详解
Mar 24 Javascript
JS运算符优先级与表达式示例详解
Sep 04 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
咖啡豆的最常见发酵处理方法,详细了解一下
2021/03/03 冲泡冲煮
Yii使用ajax验证显示错误messagebox的解决方法
2014/12/03 PHP
php判断文件上传类型及过滤不安全数据的方法
2014/12/17 PHP
Yii2框架可逆加密简单实现方法
2017/08/25 PHP
php微信公众号开发之秒杀
2018/10/20 PHP
PHP filter_var() 函数, 验证判断EMAIL,URL等
2021/03/09 PHP
dojo 之基础篇(三)之向服务器发送数据
2007/03/24 Javascript
基于jQuery的淡入淡出可自动切换的幻灯插件
2010/08/24 Javascript
ajax不执行success回调而是执行了error回调
2012/12/10 Javascript
页面加载完成后再执行JS的jquery写法以及区别说明
2014/02/22 Javascript
Jquery 实现grid绑定模板
2015/01/28 Javascript
jQuery+CSS3文字跑马灯特效的简单实现
2016/06/25 Javascript
vue2中filter()的实现代码
2017/07/09 Javascript
JS简单实现数组去重的方法分析
2017/10/14 Javascript
webpack 3.X学习之多页面打包的方法
2018/09/04 Javascript
[05:05]DOTA2亚洲邀请赛 战队出场仪式
2015/02/07 DOTA
python爬虫爬取某站上海租房图片
2018/02/04 Python
python3.6+opencv3.4实现鼠标交互查看图片像素
2018/02/26 Python
如何利用Python分析出微信朋友男女统计图
2019/01/25 Python
详解Python 爬取13个旅游城市,告诉你五一大家最爱去哪玩?
2019/05/07 Python
Python面向对象进阶学习
2019/05/21 Python
django admin 自定义替换change页面模板的方法
2019/08/23 Python
python使用yield压平嵌套字典的超简单方法
2019/11/02 Python
Python 实现判断图片格式并转换,将转换的图像存到生成的文件夹中
2020/01/13 Python
借助Paramiko通过Python实现linux远程登陆及sftp的操作
2020/03/16 Python
基于tf.shape(tensor)和tensor.shape()的区别说明
2020/06/30 Python
Python获取excel内容及相关操作代码实例
2020/08/10 Python
python遍历路径破解表单的示例
2020/11/21 Python
pycharm配置安装autopep8自动规范代码的实现
2021/03/02 Python
解决HTML5中的audio在手机端和微信端的不能自动播放问题
2019/11/04 HTML / CSS
合作合同协议书范本
2015/01/27 职场文书
我的中国梦主题教育活动总结
2015/05/07 职场文书
2015年小学美术工作总结
2015/05/25 职场文书
学雷锋广播稿大全
2015/08/19 职场文书
Nginx配置https的实现
2021/11/27 Servers
JS前端使用Canvas快速实现手势解锁特效
2022/09/23 Javascript