js+贝塞尔曲线+animate动画
创始人
2024-01-28 04:59:41
0

文章目录

  • 一 介绍
  • 二 示例
    • 1阶贝塞尔曲线
    • 2阶贝塞尔曲线
    • 3阶贝塞尔曲线:
    • 4/n阶贝塞尔曲线
  • 三 封装和使用
    • bezier.js
    • App.jsx
    • App.scss

一 介绍

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。

下面是我们最常用到bezier曲线的地方

  • svg
  • canvas/webgl
  • css3 动画
  • animation

下面我们将用js来实现贝塞尔曲线的画制

通用的贝塞尔曲线公式:
贝塞尔曲线公式
由此公式可计算得到下面的n阶贝塞尔曲线各个坐标点

二 示例

1阶贝塞尔曲线

    /*** @desc 一阶贝塞尔* @param {number} t 当前百分比* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标*/oneBezier(t, p1, p2) {const [x1, y1] = p1;const [x2, y2] = p2;let x = x1 + (x2 - x1) * t;let y = y1 + (y2 - y1) * t;return [x, y];}

1阶贝塞尔曲线:
上图为1阶贝塞尔的绘制,其实只是从起点(-17,285)到终点(1920,89)的直线运动,中间并没有改变运动轨迹

2阶贝塞尔曲线

在这里插入图片描述

    /*** @desc 二阶贝塞尔* @param {number} t 当前百分比* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标* @param {Array} cp 控制点*/twoBezier(t, p1, cp, p2) {const [x1, y1] = p1;const [cx, cy] = cp;const [x2, y2] = p2;let x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2;let y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2;return [x, y];}

2阶贝塞尔曲线
上图为2阶贝塞尔的绘制,是小球从起点p0(180,22)到终点p2(1920,89)的匀速运动,中间受到p1(800,0)坐标影响改变运动轨迹,形成的曲线的运动轨迹

3阶贝塞尔曲线:

在这里插入图片描述

    /*** @desc 三阶贝塞尔* @param {number} t 当前百分比* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标* @param {Array} cp1 控制点1* @param {Array} cp2 控制点2*/threeBezier(t, p1, cp1, cp2, p2) {const [x1, y1] = p1;const [x2, y2] = p2;const [cx1, cy1] = cp1;const [cx2, cy2] = cp2;let x =x1 * (1 - t) * (1 - t) * (1 - t) +3 * cx1 * t * (1 - t) * (1 - t) +3 * cx2 * t * t * (1 - t) +x2 * t * t * t;let y =y1 * (1 - t) * (1 - t) * (1 - t) +3 * cy1 * t * (1 - t) * (1 - t) +3 * cy2 * t * t * (1 - t) +y2 * t * t * t;return [x, y];}

3阶贝塞尔曲线
上图为3阶贝塞尔的绘制,是小球从起点p0(0,500)到终点p3(1920,0)的匀速运动,中间受到p1(300,0),p2(1160,500)坐标影响改变运动轨迹,形成的曲线的运动轨迹

4/n阶贝塞尔曲线

在这里插入图片描述

    /*** 多阶贝塞尔曲线的生成* @param {*} anchorpoints 贝塞尔基点* @param {*} pointsAmount 生成的点数* @returns 路径点的Array*/CreateBezierPoints(anchorpoints, pointsAmount) {let last = anchorpoints[anchorpoints.length-1]var points = [];for (var i = 0; i < pointsAmount; i++) {var point = this.MultiPointBezier(anchorpoints, i / pointsAmount);points.push(point);}return points;}MultiPointBezier(points, t) {var len = points.length;var x = 0, y = 0;var erxiangshi = function (start, end) {var cs = 1, bcs = 1;while (end > 0) {cs *= start;bcs *= end;start--;end--;}return (cs / bcs);};for (var i = 0; i < len; i++) {var point = points[i];x += point[0] * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));y += point[1] * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));}return [x,y];}

4阶贝塞尔曲线
上图为4阶贝塞尔的绘制,是小球从起点p0(-17,285)到终点p4(1920,89)的匀速运动,中间受到p1(180,22) p2(1160,1102) p3(1350,-44)坐标影响改变运动轨迹,形成的曲线的运动轨迹

下面为完整的封装和使用:

三 封装和使用

bezier.js

/*** @desc 贝塞尔曲线算法,包含了3阶贝塞尔*/
class Bezier {/*** @desc 获取点,这里可以设置点的个数* @param {number} num 点个数* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标* @param {Array} p3 点坐标* @param {Array} p4 点坐标* 如果参数是 num, p1, p2 为一阶贝塞尔* 如果参数是 num, p1, c1, p2 为二阶贝塞尔* 如果参数是 num, p1, c1, c2, p2 为三阶贝塞尔*/getBezierPoints(num = 100, p1, p2, p3, p4) {let func;const points = [];if (!p3 && !p4) {func = this.oneBezier;} else if (p3 && !p4) {func = this.twoBezier;} else if (p3 && p4) {func = this.threeBezier;} else {return}for (let i = 0; i < num; i++) {points.push(func(i / num, p1, p2, p3, p4));}if (p4) {points.push([...p4]);} else if (p3) {points.push([...p3]);}return points;}/*** @desc 一阶贝塞尔* @param {number} t 当前百分比* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标*/oneBezier(t, p1, p2) {const [x1, y1] = p1;const [x2, y2] = p2;let x = x1 + (x2 - x1) * t;let y = y1 + (y2 - y1) * t;return [x, y];}/*** @desc 二阶贝塞尔* @param {number} t 当前百分比* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标* @param {Array} cp 控制点*/twoBezier(t, p1, cp, p2) {const [x1, y1] = p1;const [cx, cy] = cp;const [x2, y2] = p2;let x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * cx + t * t * x2;let y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * cy + t * t * y2;return [x, y];}/*** @desc 三阶贝塞尔* @param {number} t 当前百分比* @param {Array} p1 起点坐标* @param {Array} p2 终点坐标* @param {Array} cp1 控制点1* @param {Array} cp2 控制点2*/threeBezier(t, p1, cp1, cp2, p2) {const [x1, y1] = p1;const [x2, y2] = p2;const [cx1, cy1] = cp1;const [cx2, cy2] = cp2;let x =x1 * (1 - t) * (1 - t) * (1 - t) +3 * cx1 * t * (1 - t) * (1 - t) +3 * cx2 * t * t * (1 - t) +x2 * t * t * t;let y =y1 * (1 - t) * (1 - t) * (1 - t) +3 * cy1 * t * (1 - t) * (1 - t) +3 * cy2 * t * t * (1 - t) +y2 * t * t * t;return [x, y];}/*** 多阶贝塞尔曲线的生成* @param {*} anchorpoints 贝塞尔基点数组* @param {*} pointsAmount 生成的点数* @returns 路径点的Array*/CreateBezierPoints(anchorpoints, pointsAmount) {// let last = anchorpoints[anchorpoints.length - 1]let points = [];for (let i = 0; i < pointsAmount; i++) {let point = this.MultiPointBezier(anchorpoints, i / pointsAmount);points.push(point);}return points;}MultiPointBezier(points, t) {let len = points.length;let x = 0; let y = 0;let erxiangshi = function (start, end) {let cs = 1; let bcs = 1;while (end > 0) {cs *= start;bcs *= end;start--;end--;}return (cs / bcs);};for (let i = 0; i < len; i++) {let point = points[i];x += point[0] * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));y += point[1] * Math.pow((1 - t), (len - 1 - i)) * Math.pow(t, i) * (erxiangshi(len - 1, i));}return [x, y];}
}export default new Bezier();

App.jsx

import './App.scss';
import { useEffect, useState, Fragment } from 'react';
import bezier from './utils/bezier'
import logo from './logo.svg'
import { WOW } from 'wowjs'
import 'animate.css';
// import 'wowjs/css/libs/animate.css';function App() {let [w_width, setWWidth] = useState(window.innerWidth)  //设置幕布宽度let [h_height, setHeight] = useState(500)  //设置幕布高度let [begin_n, setBeginN] = useState([0, 164])  //开始坐标let [end_n, setEndN] = useState([w_width, 40]) //结束坐标let [one_n, setOneN] = useState([100, 40]) //bezier坐标1let [two_n, setTwoN] = useState([750, 788]) //bezier坐标2let [three_n, setThreeN] = useState([800, -30]) //bezier坐标3let [one_dot_n, setOneDotN] = useState(40) //生成背景虚线坐标的数量let [two_dot_n, setTwoDotN] = useState(10) //生成上层圆的数量let [oneXY, setOneXY] = useState() //背景虚线坐标数组let [twoXY, setTwoXY] = useState() //上层圆坐标数组let [bezier_n, setBezierN] = useState(1) //bezier阶数useEffect(() => {window.addEventListener('resize', () => {// 只要窗口大小发生像素变化就会触发setWWidth(window.innerWidth)})return () => {window.removeEventListener('resize', () => { })}}, [])//根据当前屏幕宽度计算各个贝塞尔点坐标useEffect(() => {console.log('当前屏幕宽', w_width)setBeginN([(-17 / 1920) * w_width, (255 / 446) * h_height])setEndN([(1920 / 1920) * w_width, (80 / 446) * h_height])setOneN([(180 / 1920) * w_width, (20 / 446) * h_height])setTwoN([(1160 / 1920) * w_width, (983 / 446) * h_height])setThreeN([(1350 / 1920) * w_width, (-40 / 446) * h_height])}, [w_width, h_height])useEffect(() => {new WOW({live: false}).init()console.log('当前坐标组为:', [begin_n, one_n, two_n, three_n, end_n])let anchorpointsswitch (bezier_n) {case 1:anchorpoints = [begin_n, end_n]break;case 2:anchorpoints = [begin_n, one_n, end_n]break;case 3:anchorpoints = [begin_n, one_n, two_n, end_n]break;default:anchorpoints = [begin_n, one_n, two_n, three_n, end_n]break;}let oneXY = bezier.CreateBezierPoints(anchorpoints,one_dot_n)//计算背景虚线的斜率oneXY.forEach((value, key) => {if (oneXY[key + 1]) {let nextX = oneXY[key + 1][0]let nextY = oneXY[key + 1][1]let thisX = value[0]let thisY = value[1]let xl = (((nextY - thisY) / (nextX - thisX)) * 100) / 2oneXY[key] = [...value, xl]// console.log(`第${key}个坐标斜率为:${xl}`)// console.log(value)}})// console.log(oneXY)setOneXY(oneXY)let twoXY = bezier.CreateBezierPoints(anchorpoints,two_dot_n)setTwoXY(twoXY)}, [begin_n, one_n, two_n, three_n, end_n, one_dot_n, two_dot_n, bezier_n])let arrayChange = (array, number, element) => {let newValue = [...array]newValue[number] = parseFloat(element.target.value)console.log(newValue)return newValue}return (

贝塞尔曲线参数:
幕布宽 w_width} onChange={(el) => { setWWidth(el.target.value) }} />
幕布高度 h_height} onChange={(el) => { setHeight(el.target.value) }} />
Bezier阶数 bezier_n} onChange={(el) => { setBezierN(parseInt(el.target.value)) }} />

起点:begin_n[0]} onChange={(el) => { setBeginN(arrayChange(begin_n, 0, el)) }} />begin_n[1]} onChange={(el) => { setBeginN(arrayChange(begin_n, 1, el)) }} />
1点:one_n[0]} onChange={(el) => { setOneN(arrayChange(one_n, 0, el)) }} />one_n[1]} onChange={(el) => { setOneN(arrayChange(one_n, 1, el)) }} />
2点:two_n[0]} onChange={(el) => { setTwoN(arrayChange(two_n, 0, el)) }} />two_n[1]} onChange={(el) => { setTwoN(arrayChange(two_n, 1, el)) }} />
3点:three_n[0]} onChange={(el) => { setThreeN(arrayChange(three_n, 0, el)) }} />three_n[1]} onChange={(el) => { setThreeN(arrayChange(three_n, 1, el)) }} />
终点:end_n[0]} onChange={(el) => { setEndN(arrayChange(end_n, 0, el)) }} />end_n[1]} onChange={(el) => { setEndN(arrayChange(end_n, 1, el)) }} />
曲线数量:one_dot_n} onChange={(el) => { setOneDotN(el.target.value) }} />
元素数量:two_dot_n} onChange={(el) => { setTwoDotN(el.target.value) }} />
{width: `${w_width}px`,height: `${h_height}px`}}>{oneXY ? oneXY.map((v, k) => {return (`${k}one`}className={`dot${k} wow`}data-wow-delay={`${k * 60}ms`}data-wow-duration="1s"style={{left: `${v[0]}px`,top: `${v[1]}px`,transform: `rotate(${v[2]}deg) translate(-50%, -50%)`,}}>)}) : ''}{twoXY ? twoXY.map((v, k) => {return (`domain-infos${k} wow`}key={`${k}two`}data-wow-delay={`${k * 300 + 2000}ms`}data-wow-duration="4s"style={{left: `${v[0]}px`,top: `${v[1]}px`,display: k > 0 ? '' : 'none','flexDirection': k % 2 === 0 ? 'column-reverse' : 'column',}}>logo} alt="" />
[{parseInt(v[0])},{parseInt(v[1])}]
)}) : ''}
); }export default App;

App.scss


@keyframes dotchange {0% {opacity: 0;visibility: hidden;}20% {opacity: .2;visibility: visible;}40% {opacity: .4;visibility: visible;}60% {opacity: .6;visibility: visible;}80% {opacity: .8;visibility: visible;}100% {opacity: 1;visibility: visible;}
}@keyframes mymove {0% {top: 0px;}30% {top: -10px;}60% {top: -20px;}100% {top: -32px;}
}.App {width: 100%;background: pink;height: 100vh;color: #000000;
}h2 {font-size: 15px;line-height: 15px;// color: #ffffff;text-align: center;
}.inputNum {width: 60%;padding-top: 50px;margin: 50px auto;display: flex;align-items: center;.right {margin-left: 40px;}.param_group {margin-top: 5px;display: flex;justify-content: center;align-items: center;input {width: 60px;z-index: 12;}}
}.main-container {margin: 10px auto;width: 100%;height: 500px;background: #00022f;position: relative;overflow-x: clip;
}[class^=dot] {position: absolute;width: 6px;height: 3px;border-radius: 4px;display: inline-block;background: #34ccff;font-size: 12px;color: #ccc;visibility: hidden;opacity: 0;animation: dotchange linear;animation-fill-mode: both;
}[class^=domain-infos] {position: absolute;display: flex;align-items: center;flex-direction: column;width: 60px;height: 60px;visibility: hidden;opacity: 0;animation: dotchange linear;animation-fill-mode: both;transform: translate(-50%, -50%);&:hover {.domain-img_o {visibility: visible !important;opacity: 1 !important;.domain-img_item {visibility: visible !important;}}}&:hover {.domain-name {opacity: 1;}}.domain-img_o {border-radius: 50%;background: rgba(52, 204, 255, 0.2);animation: dotchange linear;animation-fill-mode: both;display: flex;align-items: center;justify-content: center;.domain-img_item {width: 60px;height: 60px;border-radius: 50%;}}.domain-name {padding: 20px 0;font-family: 'DINNextLTPro-Regular';font-weight: 700;font-size: 16px;line-height: 19px;color: #ffffff;opacity: 0.5;text-align: center;}
}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...