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 相关文章推荐
ASP.NET MVC中EasyUI的datagrid跨域调用实现代码
Mar 14 Javascript
jQuery 无限级菜单的简单实例
Feb 21 Javascript
在Google 地图上实现做的标记相连接
Jan 05 Javascript
JavaScript实现的简单烟花特效代码
Oct 20 Javascript
jQuery插件EasyUI校验规则 validatebox验证框
Nov 29 Javascript
浅谈JS中String()与 .toString()的区别
Oct 20 Javascript
使用layer弹窗和layui表单实现新增功能
Aug 09 Javascript
JavaScript常用数组操作方法,包含ES6方法
May 10 Javascript
从源码里了解vue中的nextTick的使用
Nov 22 Javascript
原生js实现公告滚动效果
Jan 10 Javascript
详解JavaScript中Arguments对象用途
Aug 30 Javascript
微信小程序中使用vant框架的具体步骤
Feb 18 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
Yii2使用自带的UploadedFile实现的文件上传
2016/06/20 PHP
ThinkPHP实现分页功能
2017/04/28 PHP
javascript中的继承实例代码
2011/04/27 Javascript
js数组方法扩展实现数组统计函数
2014/04/09 Javascript
jQuery取得设置清空select选择的文本与值
2014/07/08 Javascript
javascript经典特效分享 手风琴、轮播图、图片滑动
2016/09/14 Javascript
深入学习Bootstrap表单
2016/12/13 Javascript
概述jQuery中的ajax方法
2016/12/16 Javascript
vue实现ajax滚动下拉加载,同时具有loading效果(推荐)
2017/01/11 Javascript
微信小程序模板之分页滑动栏
2017/02/10 Javascript
iOS + node.js使用Socket.IO框架进行实时通信示例
2017/04/14 Javascript
简单谈谈React中的路由系统
2017/07/25 Javascript
JS+HTML实现的圆形可点击区域示例【3种方法】
2018/08/01 Javascript
jQuery实现根据身份证号获取生日、年龄、性别等信息的方法
2019/01/09 jQuery
基于vue的验证码组件的示例代码
2019/01/22 Javascript
解决layui追加或者动态修改的表单元素“没效果”的问题
2019/09/18 Javascript
vue.js路由mode配置之去掉url上默认的#方法
2019/11/01 Javascript
javascript 数组精简技巧小结
2020/02/26 Javascript
Python正则表达式介绍
2012/08/06 Python
详解使用 pyenv 管理多个版本 python 环境
2017/10/19 Python
Python并发编程协程(Coroutine)之Gevent详解
2017/12/27 Python
python实现简易通讯录修改版
2018/03/13 Python
python 二维矩阵转三维矩阵示例
2019/11/30 Python
Giuseppe Zanotti美国官方网站:将鞋履视为高级时装般精心制作
2018/02/06 全球购物
病媒生物防治方案
2014/05/13 职场文书
物流管理专业求职信
2014/05/29 职场文书
商业街策划方案
2014/05/31 职场文书
公司外出活动方案
2014/08/14 职场文书
委托培训协议书
2014/11/17 职场文书
2015年初中生自我评价范文
2015/03/03 职场文书
初级职称评定工作总结
2015/08/13 职场文书
六种css3实现的边框过渡效果
2021/04/22 HTML / CSS
python使用openpyxl库读写Excel表格的方法(增删改查操作)
2021/05/02 Python
MySQL分区表实现按月份归类
2021/11/01 MySQL
Python OpenCV超详细讲解调整大小与图像操作的实现
2022/04/02 Python
MySQL分布式恢复进阶
2022/07/23 MySQL