├── .babelrc ├── .gitignore ├── README-CN.md ├── README.md ├── asset ├── bz-box.html ├── curvejs.ico ├── get-path.html ├── github.png ├── main.js ├── rope.html ├── smooth.html ├── smooth2.html ├── smooth2.js └── test.png ├── dist ├── curve.js └── curve.min.js ├── example ├── curves │ ├── index.html │ ├── index.js │ └── main.js ├── expand │ ├── index.html │ ├── index.js │ └── main.js ├── loading │ ├── index.html │ ├── loading.js │ └── qq.png ├── noise-motion │ ├── index.html │ ├── index.js │ └── main.js ├── noise │ ├── index.html │ ├── index.js │ └── main.js ├── points-to │ ├── index.html │ ├── index.js │ └── main.js ├── rope │ ├── index.html │ ├── index.js │ └── main.js ├── scale-to │ ├── index.html │ ├── index.js │ └── main.js ├── simple-es5 │ └── index.html ├── simple │ ├── index.html │ ├── index.js │ └── main.js ├── siri-wave │ ├── index.html │ ├── index.js │ ├── main.js │ └── siri-wava.js ├── smooth │ ├── index.html │ ├── index.js │ └── main.js ├── sprout │ ├── index.html │ ├── index.js │ └── main.js ├── tfc │ ├── index-sc.html │ ├── index.html │ ├── index.js │ ├── main-sc.js │ └── main.js ├── water │ ├── bg.png │ ├── index.html │ ├── index.js │ └── main.js └── word │ ├── index.html │ ├── index.js │ └── main.js ├── index.html ├── package.json ├── pg ├── index.html ├── preview.html └── rd.html ├── rollup.config.js ├── src ├── color.js ├── curve.js ├── group.js ├── index.js ├── motion │ ├── circle.js │ ├── dance.js │ ├── expand.js │ ├── index.js │ ├── line.js │ ├── move.js │ ├── noise.js │ ├── path.js │ ├── rotate.js │ └── to.js ├── noise.js ├── q-curve.js ├── raf.js ├── smooth-curve.js ├── sprout-curve.js ├── stage.js ├── util.js ├── vector2.js ├── word-data.js └── word.js └── tutorial ├── rope.md └── smooth-curve.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015" ] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | ## 腾讯AlloyTeam正式发布Web魔幻线条 - curvejs 2 | 3 | ![](http://images2015.cnblogs.com/blog/105416/201704/105416-20170423095323554-971072124.png) 4 | 5 | [curvejs](https://github.com/AlloyTeam/curvejs) 中文读["克js"],是腾讯AlloyTeam打造的一款魔幻线条框架,让线条成为一名优秀的舞者,让线条们成为优秀的舞团,HTML5 Canvas就是舞台。 6 | 7 | 官网:[https://alloyteam.github.io/curvejs/](https://alloyteam.github.io/curvejs/) 8 | 9 | 你还记得window经典的屏幕保护程序《变幻线》吗? 10 | 11 | ![](http://images2015.cnblogs.com/blog/105416/201704/105416-20170421100349227-820259243.png) 12 | 13 | 其原理就是使用 Perlin-Noise + Particle System + Bézier Curve + Color Transition 制作而成。 14 | 15 | 使用curvejs实现类似变幻线功能只需要几行代码: 16 | 17 | ```js 18 | const { Stage, Curve, motion } = curvejs 19 | 20 | let stage = new Stage(document.getElementById('myCanvas')) 21 | 22 | stage.add(new Curve({ 23 | color: '#00FF00', 24 | data: {value: 0, step: 0.008, width: 600, height: 400}, 25 | motion: motion.noise 26 | })) 27 | 28 | function tick(){ 29 | stage.update() 30 | requestAnimationFrame(tick) 31 | } 32 | 33 | tick() 34 | ``` 35 | 36 | [【体验地址】](https://alloyteam.github.io/curvejs/pg/rd.html?type=noise) 37 | 38 | 当然,curvejs的能力不仅仅是变换线,这完全取决于你的想象力。比如: 39 | 40 | * [Loading](https://alloyteam.github.io/curvejs/example/loading/) 41 | * [Rope](https://alloyteam.github.io/curvejs/example/rope/) 42 | * [Siri-Wave](https://alloyteam.github.io/curvejs/example/siri-wave/) 43 | * [Water](https://alloyteam.github.io/curvejs/example/water/) 44 | * [Sprout](https://alloyteam.github.io/curvejs/example/sprout/) 45 | * [Scale-To](https://alloyteam.github.io/curvejs/pg/rd.html?type=scale) 46 | * [Points-To](https://alloyteam.github.io/curvejs/pg/rd.html?type=points-to) 47 | * [Rotate](https://alloyteam.github.io/curvejs/pg/rd.html?type=rotate) 48 | * [Word](https://alloyteam.github.io/curvejs/pg/rd.html?type=word) 49 | * [Perlin-Noise](https://alloyteam.github.io/curvejs/pg/rd.html?type=noise) 50 | * [Simple](https://alloyteam.github.io/curvejs/pg/rd.html?type=simple) 51 | * [Simple-ES5](https://alloyteam.github.io/curvejs/pg/rd.html?type=simple-es5) 52 | * [Lerp Color](https://alloyteam.github.io/curvejs/pg/rd.html?type=color) 53 | * [Curves](https://alloyteam.github.io/curvejs/pg/rd.html?type=curves) 54 | * [Line](https://alloyteam.github.io/curvejs/pg/rd.html?type=line) 55 | * [Close](https://alloyteam.github.io/curvejs/pg/rd.html?type=close) 56 | 57 | ## 使用指南 58 | 59 | ```bash 60 | $ npm install curvejs 61 | ``` 62 | 63 | ```javascript 64 | import curvejs from 'curvejs' 65 | ``` 66 | 67 | 也可以直接插入script到你的HTML页面: 68 | 69 | ```html 70 | 71 | ``` 72 | 73 | 开始跳舞: 74 | 75 | ```js 76 | var Stage = curvejs.Stage, 77 | Curve = curvejs.Curve, 78 | canvas = document.getElementById('myCanvas'), 79 | stage = new Stage(canvas), 80 | rd = function() { 81 | return -2 + Math.random() * 2 82 | } 83 | 84 | var curve = new Curve({ 85 | color: '#00FF00', 86 | points: [277, 327, 230, 314, 236, 326, 257, 326], 87 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 88 | motion: function motion(points, data) { 89 | points.forEach(function (item, index) { 90 | points[index] += data[index] 91 | }) 92 | } 93 | }) 94 | 95 | stage.add(curve) 96 | 97 | function tick(){ 98 | stage.update() 99 | requestAnimationFrame(tick) 100 | } 101 | 102 | tick() 103 | ``` 104 | 105 | 上面的points代表了三次贝塞尔曲线的4个点。motion代表运动方式,motion可以拿到points和data。motion里函数的this指向Curve是实例curve。 106 | 107 | 108 | ## 使用内置motion 109 | 110 | ```js 111 | var curve = new Curve({ 112 | points: [277, 327, 230, 314, 236, 326, 257, 326], 113 | data: {angle: 0, r:5 ,step:Math.PI / 50 }, 114 | motion: curvejs.motion.dance 115 | }) 116 | ``` 117 | 118 | ## 基本原理 119 | ![](http://images2015.cnblogs.com/blog/105416/201704/105416-20170421100408884-843332110.png) 120 | 121 | 122 | * 每次创建Curve 可以传入八个数字,其实就代表上面的4个点的坐标 123 | * motion里可以拿到 points 进行自定义变幻 124 | * 幻影不需要开发者考虑,curvejs会自动生成幻影 125 | 126 | 这里需要特别强调,curvejs的幻影不是利用canvas的黑色底,然后fillRect填充半透而产生,而是Particle System。所以curvejs制作出的效果不用一定是黑色背景,而且canvas也可以是透明,这就大大增加了适用场景。 127 | 128 | ## 提交你的motion 129 | 130 | 在 [ motion 目录](https://github.com/AlloyTeam/curvejs/tree/master/src/motion), 有许多内置的motion提供给开发者使用,但是你也可以提交你的motion到这个项目,我会第一时间review并合入主干。 131 | 132 | 基本motion格式规则: 133 | 134 | ```js 135 | /** 136 | * motion description. 137 | * 138 | * @param {points} 139 | * @param {data} 140 | * data rule example: 141 | * [1, 0.2, -3, 0.7, 0.5, 0.3, -1, 1] 142 | */ 143 | export default function (points, data) { 144 | //你的motion逻辑 145 | } 146 | ``` 147 | 148 | ## curvejs相关 149 | 150 | * 官网:[https://alloyteam.github.io/curvejs/](https://alloyteam.github.io/curvejs/) 151 | * Github: [https://github.com/AlloyTeam/curvejs](https://github.com/AlloyTeam/curvejs) 152 | * 更加方便的交流关于curvejs的一切可以加入QQ的curvejs交流群(179181560) 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 

2 | curvejs 3 |

4 |

5 | Made curve a dancer in HTML5 canvas. 6 |

7 | 8 | --- 9 | ## [中文README](https://github.com/AlloyTeam/curvejs/blob/master/README-CN.md) 10 | 11 | ## Demos 12 | 13 | * [Loading](https://alloyteam.github.io/curvejs/example/loading/) 14 | * [Rope](https://alloyteam.github.io/curvejs/example/rope/) 15 | * [Siri-Wave](https://alloyteam.github.io/curvejs/example/siri-wave/) 16 | * [Water](https://alloyteam.github.io/curvejs/example/water/) 17 | * [Sprout](https://alloyteam.github.io/curvejs/example/sprout/) 18 | * [Scale-To](https://alloyteam.github.io/curvejs/pg/rd.html?type=scale) 19 | * [Points-To](https://alloyteam.github.io/curvejs/pg/rd.html?type=points-to) 20 | * [Rotate](https://alloyteam.github.io/curvejs/pg/rd.html?type=rotate) 21 | * [Word](https://alloyteam.github.io/curvejs/pg/rd.html?type=word) 22 | * [Perlin-Noise](https://alloyteam.github.io/curvejs/pg/rd.html?type=noise) 23 | * [Simple](https://alloyteam.github.io/curvejs/pg/rd.html?type=simple) 24 | * [Simple-ES5](https://alloyteam.github.io/curvejs/pg/rd.html?type=simple-es5) 25 | * [Lerp Color](https://alloyteam.github.io/curvejs/pg/rd.html?type=color) 26 | * [Curves](https://alloyteam.github.io/curvejs/pg/rd.html?type=curves) 27 | * [Line](https://alloyteam.github.io/curvejs/pg/rd.html?type=line) 28 | * [Close](https://alloyteam.github.io/curvejs/pg/rd.html?type=close) 29 | 30 | ## Usage 31 | 32 | ```bash 33 | $ npm install curvejs 34 | ``` 35 | 36 | ```javascript 37 | import curvejs from 'curvejs' 38 | ``` 39 | 40 | Or get it by the cdn and link `curve.min.js` in your HTML: 41 | 42 | ```html 43 | 44 | ``` 45 | 46 | Then start to dance: 47 | 48 | ```js 49 | var Stage = curvejs.Stage, 50 | Curve = curvejs.Curve, 51 | canvas = document.getElementById('myCanvas'), 52 | stage = new Stage(canvas), 53 | rd = function() { 54 | return -2 + Math.random() * 2 55 | } 56 | 57 | var curve = new Curve({ 58 | color: '#00FF00', 59 | points: [277, 327, 230, 314, 236, 326, 257, 326], 60 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 61 | motion: function motion(points, data) { 62 | points.forEach(function (item, index) { 63 | points[index] += data[index] 64 | }) 65 | } 66 | }) 67 | 68 | stage.add(curve) 69 | 70 | function tick(){ 71 | stage.update() 72 | requestAnimationFrame(tick) 73 | } 74 | 75 | tick() 76 | ``` 77 | 78 | ## Using built-in motion 79 | 80 | ```js 81 | var curve = new Curve({ 82 | points: [277, 327, 230, 314, 236, 326, 257, 326], 83 | data: {angle: 0, r:5 ,step:Math.PI / 50 }, 84 | motion: curvejs.motion.dance 85 | }) 86 | ``` 87 | 88 | ## Submit your motion 89 | 90 | In [this motion directory](https://github.com/AlloyTeam/curvejs/tree/master/src/motion), there are already some built-in motion. you can submit your motion and then create a pull request to the project. 91 | 92 | Format and specification of your motion: 93 | 94 | ```js 95 | /** 96 | * move motion. 97 | * 98 | * @param {points} 99 | * @param {data} 100 | * data rule example: 101 | * [1, 0.2, -3, 0.7, 0.5, 0.3, -1, 1] 102 | */ 103 | export default function (points, data) { 104 | points.forEach(function (item, index) { 105 | points[index] += data[index] 106 | }) 107 | } 108 | ``` 109 | 110 | ## QQ Group 111 | 112 | The group id is 179181560. Welcome to join the group. 113 | 114 | ## License 115 | 116 | [MIT](http://opensource.org/licenses/MIT) 117 | 118 | Copyright (c) 2017-present, dntzhang & AlloyTeam 119 | -------------------------------------------------------------------------------- /asset/bz-box.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /asset/curvejs.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlloyTeam/curvejs/b67e22e1f4e6a27ba5817ab9e3cf375f7e5f1f54/asset/curvejs.ico -------------------------------------------------------------------------------- /asset/get-path.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 49 | 50 | -------------------------------------------------------------------------------- /asset/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlloyTeam/curvejs/b67e22e1f4e6a27ba5817ab9e3cf375f7e5f1f54/asset/github.png -------------------------------------------------------------------------------- /asset/main.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var util = { 8 | 9 | random: function random(min, max) { 10 | return min + Math.floor(Math.random() * (max - min + 1)); 11 | }, 12 | 13 | randomColor: function randomColor() { 14 | return ['#22CAB3', '#90CABE', '#A6EFE8', '#C0E9ED', '#C0E9ED', '#DBD4B7', '#D4B879', '#ECCEB2', '#F2ADA6', '#FF7784'][util.random(0, 9)]; 15 | // return '#'+(Math.random()*0xffffff<<0).toString(16); 16 | }, 17 | 18 | randomSpeed: function randomSpeed() { 19 | return (Math.random() > 0.5 ? 1 : -1) * Math.random() * 2; 20 | } 21 | 22 | }; 23 | 24 | (function () { 25 | 'use strict'; 26 | 27 | if (!Date.now) Date.now = function () { 28 | return new Date().getTime(); 29 | }; 30 | 31 | var vendors = ['webkit', 'moz']; 32 | for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 33 | var vp = vendors[i]; 34 | window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 35 | window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; 36 | } 37 | if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy 38 | || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 39 | var lastTime = 0; 40 | window.requestAnimationFrame = function (callback) { 41 | var now = Date.now(); 42 | var nextTime = Math.max(lastTime + 16, now); 43 | return setTimeout(function () { 44 | callback(lastTime = nextTime); 45 | }, nextTime - now); 46 | }; 47 | window.cancelAnimationFrame = clearTimeout; 48 | } 49 | })(); 50 | 51 | /** 52 | * dance motion, spin around. 53 | * 54 | * @param {points} 55 | * @param {data} 56 | * data rule example: 57 | * { angle : 0, r : 5 , step : Math.PI / 50 } 58 | */ 59 | var dance = function (points, data) { 60 | var pre = this.copyPoints, 61 | theta = data.angle, 62 | r = data.r; 63 | 64 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 65 | points[0] = pre[0] + R[0][0] * r; 66 | points[1] = pre[1] + R[1][0] * r; 67 | points[2] = pre[2] + R[0][0] * r; 68 | points[3] = pre[3] + R[1][0] * r; 69 | points[4] = pre[4] + R[0][0] * r; 70 | points[5] = pre[5] + R[1][0] * r; 71 | points[6] = pre[6] + R[0][0] * r; 72 | points[7] = pre[7] + R[1][0] * r; 73 | 74 | data.angle += data.step; 75 | }; 76 | 77 | /** 78 | * move motion. 79 | * 80 | * @param {points} 81 | * @param {data} 82 | * data rule example: 83 | * [1, 0.2, -3, 0.7, 0.5, 0.3, -1, 1] 84 | */ 85 | var move = function (points, data) { 86 | points.forEach(function (item, index) { 87 | points[index] += data[index]; 88 | }); 89 | }; 90 | 91 | /** 92 | * rotate motion. 93 | * 94 | * @param {points} 95 | * @param {data} 96 | * data rule example: 97 | * Math.PI/100 98 | */ 99 | 100 | function _rotate(x1, y1, x2, y2, theta, index, points) { 101 | var v = { x: x1 - x2, y: y1 - y2 }; 102 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 103 | 104 | points[index] = x2 + R[0][0] * v.x + R[0][1] * v.y; 105 | points[index + 1] = y2 + R[1][0] * v.x + R[1][1] * v.y; 106 | } 107 | 108 | function rotate(points, angle) { 109 | var centerX = (points[0] + points[6]) / 2; 110 | var centerY = (points[1] + points[7]) / 2; 111 | _rotate(points[0], points[1], centerX, centerY, angle, 0, points); 112 | _rotate(points[2], points[3], centerX, centerY, angle, 2, points); 113 | _rotate(points[4], points[5], centerX, centerY, angle, 4, points); 114 | _rotate(points[6], points[7], centerX, centerY, angle, 6, points); 115 | } 116 | 117 | function n(x, y) { 118 | var sum = x * x + y * y; 119 | if (sum === 0) return [0, 0]; 120 | var len = Math.sqrt(sum); 121 | return [x / len, y / len]; 122 | } 123 | 124 | function sl(x, y) { 125 | return x * x + y * y; 126 | } 127 | 128 | var vector2 = { 129 | n: n, 130 | sl: sl 131 | }; 132 | 133 | /** 134 | * close or open path motion. 135 | * 136 | * @param {points} 137 | */ 138 | 139 | function open(points) { 140 | var v = vector2.n(points[0] - points[6], points[1] - points[7]); 141 | 142 | points[0] += v[0]; 143 | points[1] += v[1]; 144 | 145 | points[6] -= v[0]; 146 | points[7] -= v[1]; 147 | } 148 | 149 | function close(points) { 150 | var v = vector2.n(points[0] - points[6], points[1] - points[7]); 151 | 152 | points[0] -= v[0]; 153 | points[1] -= v[1]; 154 | 155 | points[6] += v[0]; 156 | points[7] += v[1]; 157 | } 158 | 159 | function auto(points) {} 160 | 161 | var path = { 162 | close: close, 163 | open: open, 164 | auto: auto 165 | }; 166 | 167 | function to(points, target) { 168 | var va1 = [points[2] - points[0], points[3] - points[1]]; 169 | var va2 = [points[4] - points[2], points[5] - points[3]]; 170 | var va3 = [points[6] - points[4], points[7] - points[5]]; 171 | 172 | var vb1 = [target[2] - target[0], target[3] - target[1]]; 173 | var vb2 = [target[4] - target[2], target[5] - target[3]]; 174 | var vb3 = [target[6] - target[4], target[7] - target[5]]; 175 | 176 | var v1 = vector2.n(vb1[0] - va1[0], vb1[1] - va1[1]); 177 | points[0] -= v1[0]; 178 | points[1] -= v1[1]; 179 | 180 | var v2 = vector2.n(vb2[0] - va2[0], vb2[1] - va2[1]); 181 | points[4] += v2[0]; 182 | points[5] += v2[1]; 183 | 184 | var v3 = vector2.n(vb3[0] - va3[0], vb3[1] - va3[1]); 185 | points[6] += v3[0]; 186 | points[7] += v3[1]; 187 | } 188 | 189 | function line(arr) { 190 | 191 | arr[2] += arr[2] > arr[0] ? -1 : 1; 192 | arr[3] += arr[3] > arr[1] ? -1 : 1; 193 | arr[4] += arr[4] > arr[6] ? -1 : 1; 194 | arr[5] += arr[5] > arr[7] ? -1 : 1; 195 | } 196 | 197 | function circle(points, data) { 198 | var R = [[Math.cos(data.angle), -Math.sin(data.angle)], [Math.sin(data.angle), Math.cos(data.angle)]]; 199 | 200 | this.data.angle += data.step; 201 | 202 | points[0] = points[0] + R[0][0]; 203 | points[1] = points[1] + R[1][0]; 204 | points[6] = points[6] + R[0][0]; 205 | points[7] = points[7] + R[1][0]; 206 | } 207 | 208 | var motion$1 = { 209 | dance: dance, 210 | move: move, 211 | rotate: rotate, 212 | to: to, 213 | line: line, 214 | circle: circle, 215 | close: path.close, 216 | open: path.open 217 | }; 218 | 219 | var classCallCheck = function (instance, Constructor) { 220 | if (!(instance instanceof Constructor)) { 221 | throw new TypeError("Cannot call a class as a function"); 222 | } 223 | }; 224 | 225 | var createClass = function () { 226 | function defineProperties(target, props) { 227 | for (var i = 0; i < props.length; i++) { 228 | var descriptor = props[i]; 229 | descriptor.enumerable = descriptor.enumerable || false; 230 | descriptor.configurable = true; 231 | if ("value" in descriptor) descriptor.writable = true; 232 | Object.defineProperty(target, descriptor.key, descriptor); 233 | } 234 | } 235 | 236 | return function (Constructor, protoProps, staticProps) { 237 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 238 | if (staticProps) defineProperties(Constructor, staticProps); 239 | return Constructor; 240 | }; 241 | }(); 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | var inherits = function (subClass, superClass) { 252 | if (typeof superClass !== "function" && superClass !== null) { 253 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 254 | } 255 | 256 | subClass.prototype = Object.create(superClass && superClass.prototype, { 257 | constructor: { 258 | value: subClass, 259 | enumerable: false, 260 | writable: true, 261 | configurable: true 262 | } 263 | }); 264 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 265 | }; 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | var possibleConstructorReturn = function (self, call) { 278 | if (!self) { 279 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 280 | } 281 | 282 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 283 | }; 284 | 285 | var Curve = function () { 286 | function Curve(option) { 287 | classCallCheck(this, Curve); 288 | 289 | this.points = option.points || []; 290 | this.color = option.color || 'black'; 291 | this.x = option.x || 0; 292 | this.y = option.y || 0; 293 | this.vision = option.vision || []; 294 | this.visionMax = 640; 295 | this.visionInterval = option.visionInterval || 10; 296 | 297 | this._preDate = Date.now(); 298 | this._now = new Date(); 299 | 300 | this.data = option.data; 301 | 302 | this.copyPoints = this.points.slice(0); 303 | this.motion = option.motion || motion$1.dance; 304 | 305 | this._initVision(option.visionCount || 80); 306 | } 307 | 308 | createClass(Curve, [{ 309 | key: '_initVision', 310 | value: function _initVision() { 311 | var i = 0; 312 | for (; i < 80; i++) { 313 | this.tick(true); 314 | } 315 | } 316 | }, { 317 | key: 'tick', 318 | value: function tick(tickSelf) { 319 | this._now = Date.now(); 320 | if (this._now - this._preDate > this.visionInterval || tickSelf) { 321 | 322 | this.vision.push.apply(this.vision, this.points); 323 | if (this.vision.length > this.visionMax) { 324 | this.vision.splice(0, 8); 325 | } 326 | this._preDate = this._now; 327 | } 328 | 329 | this.motion.call(this, this.points, this.data); 330 | } 331 | }, { 332 | key: 'draw', 333 | value: function draw(ctx) { 334 | this.tick(); 335 | ctx.save(); 336 | ctx.translate(this.x, this.y); 337 | ctx.globalAlpha = 1; 338 | var points = this.points; 339 | ctx.beginPath(); 340 | ctx.strokeStyle = this.color; 341 | ctx.moveTo.call(ctx, points[0], points[1]); 342 | ctx.bezierCurveTo.call(ctx, points[2], points[3], points[4], points[5], points[6], points[7]); 343 | ctx.stroke(); 344 | 345 | var vision = this.vision; 346 | 347 | var i = 0, 348 | len = vision.length; 349 | for (; i < len; i += 8) { 350 | ctx.globalAlpha = i / this.visionMax * 0.1; 351 | ctx.beginPath(); 352 | ctx.strokeStyle = this.color; 353 | ctx.moveTo.call(ctx, vision[i], vision[i + 1]); 354 | ctx.bezierCurveTo.call(ctx, vision[i + 2], vision[i + 3], vision[i + 4], vision[i + 5], vision[i + 6], vision[i + 7]); 355 | ctx.stroke(); 356 | } 357 | ctx.restore(); 358 | } 359 | }, { 360 | key: 'clone', 361 | value: function clone() {} 362 | }]); 363 | return Curve; 364 | }(); 365 | 366 | var Group = function () { 367 | function Group() { 368 | classCallCheck(this, Group); 369 | 370 | this.children = []; 371 | } 372 | 373 | createClass(Group, [{ 374 | key: "add", 375 | value: function add(line) { 376 | this.children.push(line); 377 | } 378 | }, { 379 | key: "remove", 380 | value: function remove(line) { 381 | var i = 0, 382 | len = this.children.length; 383 | for (; i < len; i++) { 384 | 385 | if (line === this.children[i]) { 386 | 387 | this.children.splice(i, 1); 388 | break; 389 | } 390 | } 391 | } 392 | }, { 393 | key: "draw", 394 | value: function draw(ctx) { 395 | this.children.forEach(function (child) { 396 | child.draw(ctx); 397 | }); 398 | } 399 | }]); 400 | return Group; 401 | }(); 402 | 403 | var Stage$1 = function (_Group) { 404 | inherits(Stage, _Group); 405 | 406 | function Stage(width, height, renderTo) { 407 | classCallCheck(this, Stage); 408 | 409 | var _this = possibleConstructorReturn(this, (Stage.__proto__ || Object.getPrototypeOf(Stage)).call(this)); 410 | 411 | if (arguments.length === 1) { 412 | _this.canvas = width; 413 | _this.ctx = _this.canvas.getContext('2d'); 414 | _this.width = _this.canvas.width; 415 | _this.height = _this.canvas.height; 416 | } else if (arguments.length === 3) { 417 | _this.canvas = document.createElement('canvas'); 418 | _this.canvas.width = width; 419 | _this.canvas.height = height; 420 | _this.width = width; 421 | _this.height = height; 422 | _this.ctx = _this.canvas.getContext('2d'); 423 | document.querySelector(renderTo).appendChild(_this.canvas); 424 | } 425 | 426 | return _this; 427 | } 428 | 429 | createClass(Stage, [{ 430 | key: 'addExistLine', 431 | value: function addExistLine(process) {} 432 | }, { 433 | key: 'sameAs', 434 | value: function sameAs(index) {} 435 | }, { 436 | key: 'update', 437 | value: function update() { 438 | var _this2 = this; 439 | 440 | this.ctx.clearRect(0, 0, this.width, this.height); 441 | this.children.forEach(function (child) { 442 | child.draw(_this2.ctx); 443 | }); 444 | } 445 | }]); 446 | return Stage; 447 | }(Group); 448 | 449 | /** 450 | * Created by dntzhang on 2017/4/15. 451 | */ 452 | var data = { 453 | 454 | 'c': [[70, 70, 12, 76, 12, 123, 70, 128]], 455 | 'u': [[25, 68, 23, 140, 80, 140, 74, 100], [73, 62, 72, 90, 75, 110, 93, 134]], 456 | 'r': [[21, 62, 21, 62, 21, 128, 21, 128], [21, 128, 17, 96, 17, 68, 65, 60]], 457 | 'v': [[12, 58, 41, 132, 61, 132, 96, 58]], 458 | 'j': [[52, 48, 53, 48, 54, 49, 52, 48], [8, 175, 24, 204, 60, 197, 56, 74]], 459 | 'e': [[44, 104, 112, 52, -20, 82, 77, 136]], 's': [[81, 72, 17, 42, 105, 145, 41, 120]], 460 | 'l': [[77, 27, 30, 114, 36, 126, 57, 126]], 461 | 'i': [[56, 71, 56, 72, 59, 70, 56, 73], [47, 93, 59, 81, 47, 121, 59, 111]] 462 | 463 | }; 464 | 465 | var Word$1 = function (_Group) { 466 | inherits(Word, _Group); 467 | 468 | function Word(word, option) { 469 | classCallCheck(this, Word); 470 | 471 | var _this = possibleConstructorReturn(this, (Word.__proto__ || Object.getPrototypeOf(Word)).call(this)); 472 | 473 | option = Object.assign({ 474 | x: 0, y: 0, color: 'black' 475 | }, option); 476 | 477 | _this.x = option.x; 478 | _this.y = option.y; 479 | _this.color = option.color; 480 | _this.word = word; 481 | _this.data = option.data; 482 | _this.motion = option.motion; 483 | 484 | _this.points = data[word]; 485 | 486 | _this._init(); 487 | return _this; 488 | } 489 | 490 | createClass(Word, [{ 491 | key: '_init', 492 | value: function _init() { 493 | var _this2 = this; 494 | 495 | this.points.forEach(function (item) { 496 | 497 | _this2.add(new Curve({ 498 | x: _this2.x, 499 | y: _this2.y, 500 | points: item, 501 | color: _this2.color, 502 | data: _this2.data, 503 | motion: _this2.motion 504 | })); 505 | }); 506 | } 507 | }]); 508 | return Word; 509 | }(Group); 510 | 511 | var curvejs = { 512 | Curve: Curve, 513 | Group: Group, 514 | Stage: Stage$1, 515 | motion: motion$1, 516 | Word: Word$1 517 | }; 518 | 519 | var Stage = curvejs.Stage; 520 | var Word = curvejs.Word; 521 | var motion = curvejs.motion; 522 | 523 | 524 | var lineCount = 10; 525 | var random$1 = util.random; 526 | var randomColor$1 = util.randomColor; 527 | var randomSpeed$1 = util.randomSpeed; 528 | var stage = new Stage(500, 250, '#container'); 529 | 530 | function generatePosition() { 531 | 532 | stage.add(new Word('c', { 533 | color: '#22CAB3', 534 | motion: motion.dance, 535 | data: { angle: 0, r: 5, step: Math.PI / 50 } 536 | 537 | })); 538 | 539 | stage.add(new Word('u', { 540 | color: '#22CAB3', 541 | x: 60, 542 | motion: motion.dance, 543 | data: { angle: 0, r: 5, step: Math.PI / 50 } 544 | })); 545 | 546 | stage.add(new Word('r', { 547 | color: '#22CAB3', 548 | x: 145, 549 | motion: motion.dance, 550 | data: { angle: 0, r: 5, step: Math.PI / 50 } 551 | })); 552 | 553 | stage.add(new Word('v', { 554 | color: '#22CAB3', 555 | x: 210, 556 | y: 10, 557 | motion: motion.dance, 558 | data: { angle: 0, r: 5, step: Math.PI / 50 } 559 | })); 560 | 561 | stage.add(new Word('e', { 562 | color: '#22CAB3', 563 | x: 280, 564 | y: -5, 565 | motion: motion.dance, 566 | data: { angle: 0, r: 5, step: Math.PI / 50 } 567 | })); 568 | 569 | stage.add(new Word('j', { 570 | color: '#FF7784', 571 | x: 350, 572 | motion: motion.dance, 573 | data: { angle: 0, r: 5, step: Math.PI / 50 } 574 | })); 575 | 576 | stage.add(new Word('s', { 577 | color: '#FF7784', 578 | x: 400, 579 | motion: motion.dance, 580 | data: { angle: 0, r: 5, step: Math.PI / 50 } 581 | })); 582 | } 583 | 584 | function tick$1() { 585 | stage.update(); 586 | requestAnimationFrame(tick$1); 587 | } 588 | 589 | (function main() { 590 | generatePosition(); 591 | tick$1(); 592 | })(); 593 | 594 | }))); 595 | -------------------------------------------------------------------------------- /asset/rope.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /asset/smooth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 折线转曲线 6 | 7 | 8 | 9 | 10 | 11 | 12 | 45 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /asset/smooth2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /asset/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlloyTeam/curvejs/b67e22e1f4e6a27ba5817ab9e3cf375f7e5f1f54/asset/test.png -------------------------------------------------------------------------------- /dist/curve.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * curvejs v0.3.3 By dntzhang 3 | * Github: https://github.com/AlloyTeam/curvejs 4 | * MIT Licensed. 5 | */ 6 | 7 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):t.curvejs=i()}(this,function(){"use strict";function t(t,i,e,n,o,s,a){var r={x:t-e,y:i-n},h=[[Math.cos(o),-Math.sin(o)],[Math.sin(o),Math.cos(o)]];a[s]=e+h[0][0]*r.x+h[0][1]*r.y,a[s+1]=n+h[1][0]*r.x+h[1][1]*r.y}function i(i,e){var n=(i[0]+i[6])/2,o=(i[1]+i[7])/2;t(i[0],i[1],n,o,e,0,i),t(i[2],i[3],n,o,e,2,i),t(i[4],i[5],n,o,e,4,i),t(i[6],i[7],n,o,e,6,i)}function e(t,i){var e=t*t+i*i;if(0===e)return[0,0];var n=Math.sqrt(e);return[t/n,i/n]}function n(t,i){return t*t+i*i}function o(t){var i=M.n(t[0]-t[6],t[1]-t[7]);t[0]+=i[0],t[1]+=i[1],t[6]-=i[0],t[7]-=i[1]}function s(t){var i=M.n(t[0]-t[6],t[1]-t[7]);t[0]-=i[0],t[1]-=i[1],t[6]+=i[0],t[7]+=i[1]}function a(t){}function r(t,i){var e=[t[2]-t[0],t[3]-t[1]],n=[t[4]-t[2],t[5]-t[3]],o=[t[6]-t[4],t[7]-t[5]],s=[i[2]-i[0],i[3]-i[1]],a=[i[4]-i[2],i[5]-i[3]],r=[i[6]-i[4],i[7]-i[5]],h=M.n(s[0]-e[0],s[1]-e[1]);t[0]-=h[0],t[1]-=h[1];var l=M.n(a[0]-n[0],a[1]-n[1]);t[4]+=l[0],t[5]+=l[1];var u=M.n(r[0]-o[0],r[1]-o[1]);t[6]+=u[0],t[7]+=u[1]}function h(t){t[2]+=t[2]>t[0]?-1:1,t[3]+=t[3]>t[1]?-1:1,t[4]+=t[4]>t[6]?-1:1,t[5]+=t[5]>t[7]?-1:1}function l(t,i){var e=[[Math.cos(i.angle),-Math.sin(i.angle)],[Math.sin(i.angle),Math.cos(i.angle)]];this.data.angle+=i.step,t[0]=t[0]+e[0][0],t[1]=t[1]+e[1][0],t[6]=t[6]+e[0][0],t[7]=t[7]+e[1][0]}function u(t,i,e,n,o){var s=t.x,a=t.y,r=i.x,h=i.y,l=e.x,u=e.y,c=n.x,v=n.y,d=(r-s)*o+s,p=(h-a)*o+a,f=(l-r)*o+r,g=(u-h)*o+h,y=(c-l)*o+l,m=(v-u)*o+u,w=(f-d)*o+d,b=(g-p)*o+p;return[s,a,d,p,w,b,((y-f)*o+f-w)*o+w,((m-g)*o+g-b)*o+b]}function c(t,i,e){return d(v(t),v(i),e)}function v(t){if(I[t])return I[t];var i=null;V[t]&&(i=t,t=F(V[t]));t=t.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,function(t,i,e,n){return i+i+e+e+n+n});var e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t),n=i||t;return I[n]=e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null,I[n]}function d(t,i,e){function n(t,i){return t+Math.round(e/100*(i-t))}function o(t){t=Math.min(t,255),t=Math.max(t,0);var i=t.toString(16);return i.length<2&&(i="0"+i),i}var s={};return e<0&&(e=0),e>100&&(e=100),s.r=n(t.r,i.r),s.g=n(t.g,i.g),s.b=n(t.b,i.b),s.cssColor="#"+o(s.r)+o(s.g)+o(s.b),s.cssColor}!function(){Date.now||(Date.now=function(){return(new Date).getTime()});for(var t=["webkit","moz"],i=0;ithis.visionInterval||t)&&(this.vision.push.apply(this.vision,this.points),this.vision.push(this.color),this.vision.length>this.visionMax&&this.vision.splice(0,9),this._preDate=this._now),this.pauseMotion||this.motion.call(this,this.points,this.data),this._targetPoints&&this._pointsTo()}},{key:"pause",value:function(){this.pauseMotion=!0}},{key:"play",value:function(){this.pauseMotion=!1}},{key:"draw",value:function(t){this.tick(),t.save(),t.translate(this.x,this.y),t.globalAlpha=1,t.strokeStyle=this.color;var i=this.points;t.beginPath(),t.moveTo.call(t,i[0],i[1]),t.bezierCurveTo.call(t,i[2],i[3],i[4],i[5],i[6],i[7]),t.stroke();for(var e=this.vision,n=0,o=e.length;n=1&&(l++,v--),d>=1&&(u++,d--),f>=1&&(c++,f--)}return g},A.noiseDetail=function(t,i){t>0&&(D=t),i>0&&(C=i)},A.noiseSeed=function(t){var i=function(){var t,i,e=4294967296;return{setSeed:function(n){i=t=(null==n?Math.random()*e:n)>>>0},getSeed:function(){return t},rand:function(){return(i=(1664525*i+1013904223)%e)/e}}}();i.setSeed(t),p=new Array(4096);for(var e=0;e<4096;e++)p[e]=i.rand()};var S=function(t,i){i.value+=i.step,t[0]=i.width*A.noise(i.value+15),t[1]=i.height*A.noise(i.value+25),t[2]=i.width*A.noise(i.value+35),t[3]=i.height*A.noise(i.value+45),t[4]=i.width*A.noise(i.value+55),t[5]=i.height*A.noise(i.value+65),t[6]=i.width*A.noise(i.value+75),t[7]=i.height*A.noise(i.value+85)},E={dance:_,move:x,rotate:i,to:r,line:h,circle:l,close:P.close,open:P.open,expand:T,noise:S},j={c:[[70,70,12,76,12,123,70,128]],u:[[25,68,23,140,80,140,74,100],[73,62,72,90,75,110,93,134]],r:[[21,62,21,62,21,128,21,128],[21,128,17,96,17,68,65,60]],v:[[12,58,41,132,61,132,96,58]],e:[[44,104,112,52,-20,82,77,136]],j:[[52,48,53,48,54,49,52,48],[8,175,24,204,60,197,56,74]],s:[[81,72,17,42,105,145,41,120]],l:[[77,27,30,114,36,126,57,126]],i:[[56,71,56,72,59,70,56,73],[47,93,59,81,47,121,59,111]]},O=function(t){function i(t,e){f(this,i);var n=m(this,(i.__proto__||Object.getPrototypeOf(i)).call(this));return e=Object.assign({x:0,y:0,color:"black"},e),n.x=e.x,n.y=e.y,n.color=e.color,n.word=t,n.data=e.data,n.motion=e.motion,n.points=j[t],n._init(),n}return y(i,t),g(i,[{key:"_init",value:function(){var t=this;this.points.forEach(function(i){t.add(new w({x:t.x,y:t.y,points:i,color:t.color,data:t.data,motion:t.motion}))})}}]),i}(b),I={},V={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgrey:11119017,darkgreen:25600,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,grey:8421504,green:32768,greenyellow:11403055,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgrey:13882323,lightgreen:9498256,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662680,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14184595,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},F="1"==="01".substr(-1)?function(t){return"#"+("00000"+t.toString(16)).substr(-6)}:function(t){var i=t.toString(16);return"#"+new Array(i.length<6?6-i.length+1:0).join("0")+i},z={lerp:c,hexToRgb:v,makeGradientColor:d},R=function(){function t(i){f(this,t),this.points=i.points||[0,0,0,0,0,0],this.color=i.color||"black",this.x=i.x||0,this.y=i.y||0,this.vision=i.vision||[],this.visionMax=void 0!==i.visionMax?i.visionMax:80,this.visionInterval=i.visionInterval||10,this.disableVision=i.disableVision,this._preDate=Date.now(),this._now=new Date,this.debug=i.debug,this.size=i.size||1,this.data=i.data;var e=function(){};this._ease=function(t){return t},this._targetPoints=null,this.copyPoints=this.points.slice(0),this.motion=i.motion||e,this.visionAlpha=void 0===i.visionAlpha?.2:i.visionAlpha,(void 0===i.initVision||i.initVision)&&this._initVision(i.visionCount||80),this.beforeDraw=i.beforeDraw||e,this.afterDraw=i.afterDraw||e}return g(t,[{key:"_initVision",value:function(){for(var t=0;t<80;t++)this.tick(!0)}},{key:"tick",value:function(t){this.disableVision||(this._now=Date.now(),(this._now-this._preDate>this.visionInterval||t)&&(this.vision.push(this.points.slice(0)),this.vision.length>this.visionMax&&this.vision.splice(0,1),this._preDate=this._now)),this.pauseMotion||this.motion.call(this,this.points,this.data),this._targetPoints&&this._pointsTo()}},{key:"pause",value:function(){this.pauseMotion=!0}},{key:"play",value:function(){this.pauseMotion=!1}},{key:"draw",value:function(t){this.tick(),this.beforeDraw.call(this,t),t.save(),t.translate(this.x,this.y),t.lineWidth=this.size,t.globalAlpha=1,t.strokeStyle=this.color;var i=this.points;t.beginPath(),t.moveTo(i[0],i[1]);for(var e=2,n=i.length;e5){t.beginPath(),t.globalCompositeOperation="lighter",t.strokeStyle=this.color,t.moveTo(n[0],n[1]);for(var s=2;s 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/curves/index.js: -------------------------------------------------------------------------------- 1 | import util from '../../src/util.js' 2 | import curvejs from '../../src/index.js' 3 | 4 | const { Stage, Curve, motion } = curvejs 5 | 6 | var lineCount = 5, 7 | canvas = document.getElementById('myCanvas'), 8 | random = util.random, 9 | randomColor = util.randomColor, 10 | randomSpeed = util.randomSpeed, 11 | stage = new Stage(canvas) 12 | 13 | function generatePosition() { 14 | for (var i = 0; i < lineCount; i++) { 15 | stage.add(new Curve({ 16 | points: [random(10, canvas.width - 10), random(10, canvas.height - 10), random(10, canvas.width - 10), random(10, canvas.height - 10), random(10, canvas.width - 10), random(10, canvas.height - 10), random(10, canvas.width - 10), random(10, canvas.height - 10)], 17 | color: randomColor(), 18 | motion: motion.circle, 19 | data: { angle: 0, step: Math.PI / 100 } 20 | })) 21 | } 22 | } 23 | 24 | 25 | function tick(){ 26 | stage.update() 27 | requestAnimationFrame(tick) 28 | } 29 | 30 | ;(function main(){ 31 | generatePosition() 32 | tick() 33 | })() -------------------------------------------------------------------------------- /example/curves/main.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var util = { 8 | 9 | random: function random(min, max) { 10 | return min + Math.floor(Math.random() * (max - min + 1)); 11 | }, 12 | 13 | randomColor: function randomColor() { 14 | return ['#22CAB3', '#90CABE', '#A6EFE8', '#C0E9ED', '#C0E9ED', '#DBD4B7', '#D4B879', '#ECCEB2', '#F2ADA6', '#FF7784'][util.random(0, 9)]; 15 | // return '#'+(Math.random()*0xffffff<<0).toString(16); 16 | }, 17 | 18 | randomSpeed: function randomSpeed() { 19 | return (Math.random() > 0.5 ? 1 : -1) * Math.random() * 2; 20 | } 21 | 22 | }; 23 | 24 | (function () { 25 | 'use strict'; 26 | 27 | if (!Date.now) Date.now = function () { 28 | return new Date().getTime(); 29 | }; 30 | 31 | var vendors = ['webkit', 'moz']; 32 | for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 33 | var vp = vendors[i]; 34 | window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 35 | window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; 36 | } 37 | if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy 38 | || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 39 | var lastTime = 0; 40 | window.requestAnimationFrame = function (callback) { 41 | var now = Date.now(); 42 | var nextTime = Math.max(lastTime + 16, now); 43 | return setTimeout(function () { 44 | callback(lastTime = nextTime); 45 | }, nextTime - now); 46 | }; 47 | window.cancelAnimationFrame = clearTimeout; 48 | } 49 | })(); 50 | 51 | /** 52 | * dance motion, spin around. 53 | * 54 | * @param {points} 55 | * @param {data} 56 | * data rule example: 57 | * { angle : 0, r : 5 , step : Math.PI / 50 } 58 | */ 59 | var dance = function (points, data) { 60 | var pre = this.copyPoints, 61 | theta = data.angle, 62 | r = data.r; 63 | 64 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 65 | points[0] = pre[0] + R[0][0] * r; 66 | points[1] = pre[1] + R[1][0] * r; 67 | points[2] = pre[2] + R[0][0] * r; 68 | points[3] = pre[3] + R[1][0] * r; 69 | points[4] = pre[4] + R[0][0] * r; 70 | points[5] = pre[5] + R[1][0] * r; 71 | points[6] = pre[6] + R[0][0] * r; 72 | points[7] = pre[7] + R[1][0] * r; 73 | 74 | data.angle += data.step; 75 | }; 76 | 77 | /** 78 | * move motion. 79 | * 80 | * @param {points} 81 | * @param {data} 82 | * data rule example: 83 | * [1, 0.2, -3, 0.7, 0.5, 0.3, -1, 1] 84 | */ 85 | var move = function (points, data) { 86 | points.forEach(function (item, index) { 87 | points[index] += data[index]; 88 | }); 89 | }; 90 | 91 | /** 92 | * rotate motion. 93 | * 94 | * @param {points} 95 | * @param {data} 96 | * data rule example: 97 | * Math.PI/100 98 | */ 99 | 100 | function _rotate(x1, y1, x2, y2, theta, index, points) { 101 | var v = { x: x1 - x2, y: y1 - y2 }; 102 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 103 | 104 | points[index] = x2 + R[0][0] * v.x + R[0][1] * v.y; 105 | points[index + 1] = y2 + R[1][0] * v.x + R[1][1] * v.y; 106 | } 107 | 108 | function rotate(points, angle) { 109 | var centerX = (points[0] + points[6]) / 2; 110 | var centerY = (points[1] + points[7]) / 2; 111 | _rotate(points[0], points[1], centerX, centerY, angle, 0, points); 112 | _rotate(points[2], points[3], centerX, centerY, angle, 2, points); 113 | _rotate(points[4], points[5], centerX, centerY, angle, 4, points); 114 | _rotate(points[6], points[7], centerX, centerY, angle, 6, points); 115 | } 116 | 117 | function n(x, y) { 118 | var sum = x * x + y * y; 119 | if (sum === 0) return [0, 0]; 120 | var len = Math.sqrt(sum); 121 | return [x / len, y / len]; 122 | } 123 | 124 | function sl(x, y) { 125 | return x * x + y * y; 126 | } 127 | 128 | var vector2 = { 129 | n: n, 130 | sl: sl 131 | }; 132 | 133 | /** 134 | * close or open path motion. 135 | * 136 | * @param {points} 137 | */ 138 | 139 | function open(points) { 140 | var v = vector2.n(points[0] - points[6], points[1] - points[7]); 141 | 142 | points[0] += v[0]; 143 | points[1] += v[1]; 144 | 145 | points[6] -= v[0]; 146 | points[7] -= v[1]; 147 | } 148 | 149 | function close(points) { 150 | var v = vector2.n(points[0] - points[6], points[1] - points[7]); 151 | 152 | points[0] -= v[0]; 153 | points[1] -= v[1]; 154 | 155 | points[6] += v[0]; 156 | points[7] += v[1]; 157 | } 158 | 159 | function auto(points) {} 160 | 161 | var path = { 162 | close: close, 163 | open: open, 164 | auto: auto 165 | }; 166 | 167 | function to(points, target) { 168 | var va1 = [points[2] - points[0], points[3] - points[1]]; 169 | var va2 = [points[4] - points[2], points[5] - points[3]]; 170 | var va3 = [points[6] - points[4], points[7] - points[5]]; 171 | 172 | var vb1 = [target[2] - target[0], target[3] - target[1]]; 173 | var vb2 = [target[4] - target[2], target[5] - target[3]]; 174 | var vb3 = [target[6] - target[4], target[7] - target[5]]; 175 | 176 | var v1 = vector2.n(vb1[0] - va1[0], vb1[1] - va1[1]); 177 | points[0] -= v1[0]; 178 | points[1] -= v1[1]; 179 | 180 | var v2 = vector2.n(vb2[0] - va2[0], vb2[1] - va2[1]); 181 | points[4] += v2[0]; 182 | points[5] += v2[1]; 183 | 184 | var v3 = vector2.n(vb3[0] - va3[0], vb3[1] - va3[1]); 185 | points[6] += v3[0]; 186 | points[7] += v3[1]; 187 | } 188 | 189 | function line(arr) { 190 | 191 | arr[2] += arr[2] > arr[0] ? -1 : 1; 192 | arr[3] += arr[3] > arr[1] ? -1 : 1; 193 | arr[4] += arr[4] > arr[6] ? -1 : 1; 194 | arr[5] += arr[5] > arr[7] ? -1 : 1; 195 | } 196 | 197 | function circle(points, data) { 198 | var R = [[Math.cos(data.angle), -Math.sin(data.angle)], [Math.sin(data.angle), Math.cos(data.angle)]]; 199 | 200 | this.data.angle += data.step; 201 | 202 | points[0] = points[0] + R[0][0]; 203 | points[1] = points[1] + R[1][0]; 204 | points[6] = points[6] + R[0][0]; 205 | points[7] = points[7] + R[1][0]; 206 | } 207 | 208 | var motion$1 = { 209 | dance: dance, 210 | move: move, 211 | rotate: rotate, 212 | to: to, 213 | line: line, 214 | circle: circle, 215 | close: path.close, 216 | open: path.open 217 | }; 218 | 219 | var classCallCheck = function (instance, Constructor) { 220 | if (!(instance instanceof Constructor)) { 221 | throw new TypeError("Cannot call a class as a function"); 222 | } 223 | }; 224 | 225 | var createClass = function () { 226 | function defineProperties(target, props) { 227 | for (var i = 0; i < props.length; i++) { 228 | var descriptor = props[i]; 229 | descriptor.enumerable = descriptor.enumerable || false; 230 | descriptor.configurable = true; 231 | if ("value" in descriptor) descriptor.writable = true; 232 | Object.defineProperty(target, descriptor.key, descriptor); 233 | } 234 | } 235 | 236 | return function (Constructor, protoProps, staticProps) { 237 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 238 | if (staticProps) defineProperties(Constructor, staticProps); 239 | return Constructor; 240 | }; 241 | }(); 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | var inherits = function (subClass, superClass) { 252 | if (typeof superClass !== "function" && superClass !== null) { 253 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 254 | } 255 | 256 | subClass.prototype = Object.create(superClass && superClass.prototype, { 257 | constructor: { 258 | value: subClass, 259 | enumerable: false, 260 | writable: true, 261 | configurable: true 262 | } 263 | }); 264 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 265 | }; 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | var possibleConstructorReturn = function (self, call) { 278 | if (!self) { 279 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 280 | } 281 | 282 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 283 | }; 284 | 285 | var Curve$1 = function () { 286 | function Curve(option) { 287 | classCallCheck(this, Curve); 288 | 289 | this.points = option.points || []; 290 | this.color = option.color || 'black'; 291 | this.x = option.x || 0; 292 | this.y = option.y || 0; 293 | this.vision = option.vision || []; 294 | this.visionMax = 640; 295 | this.visionInterval = option.visionInterval || 10; 296 | 297 | this._preDate = Date.now(); 298 | this._now = new Date(); 299 | 300 | this.data = option.data; 301 | 302 | this.copyPoints = this.points.slice(0); 303 | this.motion = option.motion || motion$1.dance; 304 | 305 | this._initVision(option.visionCount || 80); 306 | } 307 | 308 | createClass(Curve, [{ 309 | key: '_initVision', 310 | value: function _initVision() { 311 | var i = 0; 312 | for (; i < 80; i++) { 313 | this.tick(true); 314 | } 315 | } 316 | }, { 317 | key: 'tick', 318 | value: function tick(tickSelf) { 319 | this._now = Date.now(); 320 | if (this._now - this._preDate > this.visionInterval || tickSelf) { 321 | 322 | this.vision.push.apply(this.vision, this.points); 323 | if (this.vision.length > this.visionMax) { 324 | this.vision.splice(0, 8); 325 | } 326 | this._preDate = this._now; 327 | } 328 | 329 | this.motion.call(this, this.points, this.data); 330 | } 331 | }, { 332 | key: 'draw', 333 | value: function draw(ctx) { 334 | this.tick(); 335 | ctx.save(); 336 | ctx.translate(this.x, this.y); 337 | ctx.globalAlpha = 1; 338 | var points = this.points; 339 | ctx.beginPath(); 340 | ctx.strokeStyle = this.color; 341 | ctx.moveTo.call(ctx, points[0], points[1]); 342 | ctx.bezierCurveTo.call(ctx, points[2], points[3], points[4], points[5], points[6], points[7]); 343 | ctx.stroke(); 344 | 345 | var vision = this.vision; 346 | 347 | var i = 0, 348 | len = vision.length; 349 | for (; i < len; i += 8) { 350 | ctx.globalAlpha = i / this.visionMax * 0.1; 351 | ctx.beginPath(); 352 | ctx.strokeStyle = this.color; 353 | ctx.moveTo.call(ctx, vision[i], vision[i + 1]); 354 | ctx.bezierCurveTo.call(ctx, vision[i + 2], vision[i + 3], vision[i + 4], vision[i + 5], vision[i + 6], vision[i + 7]); 355 | ctx.stroke(); 356 | } 357 | ctx.restore(); 358 | } 359 | }, { 360 | key: 'clone', 361 | value: function clone() {} 362 | }]); 363 | return Curve; 364 | }(); 365 | 366 | var Group = function () { 367 | function Group() { 368 | classCallCheck(this, Group); 369 | 370 | this.children = []; 371 | } 372 | 373 | createClass(Group, [{ 374 | key: "add", 375 | value: function add(line) { 376 | this.children.push(line); 377 | } 378 | }, { 379 | key: "remove", 380 | value: function remove(line) { 381 | var i = 0, 382 | len = this.children.length; 383 | for (; i < len; i++) { 384 | 385 | if (line === this.children[i]) { 386 | 387 | this.children.splice(i, 1); 388 | break; 389 | } 390 | } 391 | } 392 | }, { 393 | key: "draw", 394 | value: function draw(ctx) { 395 | this.children.forEach(function (child) { 396 | child.draw(ctx); 397 | }); 398 | } 399 | }]); 400 | return Group; 401 | }(); 402 | 403 | var Stage$1 = function (_Group) { 404 | inherits(Stage, _Group); 405 | 406 | function Stage(width, height, renderTo) { 407 | classCallCheck(this, Stage); 408 | 409 | var _this = possibleConstructorReturn(this, (Stage.__proto__ || Object.getPrototypeOf(Stage)).call(this)); 410 | 411 | if (arguments.length === 1) { 412 | _this.canvas = width; 413 | _this.ctx = _this.canvas.getContext('2d'); 414 | _this.width = _this.canvas.width; 415 | _this.height = _this.canvas.height; 416 | } else if (arguments.length === 3) { 417 | _this.canvas = document.createElement('canvas'); 418 | _this.canvas.width = width; 419 | _this.canvas.height = height; 420 | _this.width = width; 421 | _this.height = height; 422 | _this.ctx = _this.canvas.getContext('2d'); 423 | document.querySelector(renderTo).appendChild(_this.canvas); 424 | } 425 | 426 | return _this; 427 | } 428 | 429 | createClass(Stage, [{ 430 | key: 'addExistLine', 431 | value: function addExistLine(process) {} 432 | }, { 433 | key: 'sameAs', 434 | value: function sameAs(index) {} 435 | }, { 436 | key: 'update', 437 | value: function update() { 438 | var _this2 = this; 439 | 440 | this.ctx.clearRect(0, 0, this.width, this.height); 441 | this.children.forEach(function (child) { 442 | child.draw(_this2.ctx); 443 | }); 444 | } 445 | }]); 446 | return Stage; 447 | }(Group); 448 | 449 | /** 450 | * Created by dntzhang on 2017/4/15. 451 | */ 452 | var data = { 453 | 454 | 'l': [[77, 27, 30, 114, 36, 126, 57, 126]], 455 | 'i': [[56, 71, 56, 72, 59, 70, 56, 73], [47, 93, 59, 81, 47, 121, 59, 111]] 456 | 457 | }; 458 | 459 | var Word = function (_Group) { 460 | inherits(Word, _Group); 461 | 462 | function Word(word, option) { 463 | classCallCheck(this, Word); 464 | 465 | var _this = possibleConstructorReturn(this, (Word.__proto__ || Object.getPrototypeOf(Word)).call(this)); 466 | 467 | option = Object.assign({ 468 | x: 0, y: 0, color: 'black' 469 | }, option); 470 | 471 | _this.x = option.x; 472 | _this.y = option.y; 473 | _this.color = option.color; 474 | _this.word = word; 475 | _this.data = option.data; 476 | _this.motion = option.motion; 477 | 478 | _this.points = data[word]; 479 | 480 | _this._init(); 481 | return _this; 482 | } 483 | 484 | createClass(Word, [{ 485 | key: '_init', 486 | value: function _init() { 487 | var _this2 = this; 488 | 489 | this.points.forEach(function (item) { 490 | 491 | _this2.add(new Curve$1({ 492 | x: _this2.x, 493 | y: _this2.y, 494 | points: item, 495 | color: _this2.color, 496 | data: _this2.data, 497 | motion: _this2.motion 498 | })); 499 | }); 500 | } 501 | }]); 502 | return Word; 503 | }(Group); 504 | 505 | var curvejs = { 506 | Curve: Curve$1, 507 | Group: Group, 508 | Stage: Stage$1, 509 | motion: motion$1, 510 | Word: Word 511 | }; 512 | 513 | var Stage = curvejs.Stage; 514 | var Curve = curvejs.Curve; 515 | var motion = curvejs.motion; 516 | 517 | 518 | var lineCount = 5; 519 | var canvas = document.getElementById('myCanvas'); 520 | var random$1 = util.random; 521 | var randomColor$1 = util.randomColor; 522 | var randomSpeed$1 = util.randomSpeed; 523 | var stage = new Stage(canvas); 524 | 525 | function generatePosition() { 526 | for (var i = 0; i < lineCount; i++) { 527 | stage.add(new Curve({ 528 | points: [random$1(10, canvas.width - 10), random$1(10, canvas.height - 10), random$1(10, canvas.width - 10), random$1(10, canvas.height - 10), random$1(10, canvas.width - 10), random$1(10, canvas.height - 10), random$1(10, canvas.width - 10), random$1(10, canvas.height - 10)], 529 | color: randomColor$1(), 530 | motion: motion.circle, 531 | data: { angle: 0, step: Math.PI / 100 } 532 | })); 533 | } 534 | } 535 | 536 | function tick$1() { 537 | stage.update(); 538 | requestAnimationFrame(tick$1); 539 | } 540 | 541 | (function main() { 542 | generatePosition(); 543 | tick$1(); 544 | })(); 545 | 546 | }))); 547 | -------------------------------------------------------------------------------- /example/expand/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/expand/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const rd = function() { 9 | return -2 + Math.random() * 2 10 | } 11 | 12 | let curve = new Curve({ 13 | color: '#00FF00', 14 | points: [277, 327, 230, 314, 236, 326, 257, 326], 15 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 16 | motion: motion.expand, 17 | data: { value: 1, step: 0.002 } 18 | }) 19 | 20 | stage.add(curve) 21 | 22 | 23 | function tick(){ 24 | stage.update() 25 | requestAnimationFrame(tick) 26 | } 27 | 28 | tick() 29 | -------------------------------------------------------------------------------- /example/loading/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/loading/loading.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlloyTeam/curvejs/b67e22e1f4e6a27ba5817ab9e3cf375f7e5f1f54/example/loading/loading.js -------------------------------------------------------------------------------- /example/loading/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlloyTeam/curvejs/b67e22e1f4e6a27ba5817ab9e3cf375f7e5f1f54/example/loading/qq.png -------------------------------------------------------------------------------- /example/noise-motion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/noise-motion/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion, color } = curvejs 4 | 5 | let stage = new Stage(document.getElementById('myCanvas')) 6 | 7 | let curve = new Curve({ 8 | color: '#00FF00', 9 | data: {value: 0, step: 0.01, width: 600, height: 400}, 10 | motion: motion.noise 11 | }) 12 | 13 | stage.add(curve) 14 | 15 | let percent = 0, 16 | step = 1 17 | 18 | setInterval(()=>{ 19 | 20 | curve.color = color.lerp('red', 'green', percent += step) 21 | 22 | if(percent === 0 || percent === 100) step *= -1 23 | 24 | },30) 25 | 26 | 27 | function tick(){ 28 | stage.update() 29 | requestAnimationFrame(tick) 30 | } 31 | 32 | tick() 33 | -------------------------------------------------------------------------------- /example/noise/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/noise/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion, perlin } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const rd = function() { 9 | return -2 + Math.random() * 2 10 | } 11 | 12 | let curve = new Curve({ 13 | color: '#00FF00', 14 | points: [277, 327, 230, 314, 236, 326, 257, 326], 15 | data: { value:0, step:0.01, interval: 10}, 16 | motion: function motion(points, data) { 17 | points.forEach(function (item, index) { 18 | var n = (perlin.noise(data.value + index * 10)) 19 | var d = -5+ n*10 20 | points[index] += d 21 | data.value += data.step 22 | }) 23 | } 24 | }) 25 | 26 | stage.add(curve) 27 | 28 | 29 | function tick(){ 30 | stage.update() 31 | requestAnimationFrame(tick) 32 | } 33 | 34 | tick() 35 | -------------------------------------------------------------------------------- /example/points-to/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/points-to/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const data = [ 9 | [70,70,12,76,12,123,70,128], 10 | [25,68,23,140,80,140,74,100], 11 | [73,62,72,90,75,110,93,134], 12 | [21,62,21,62,21,128,21,128], 13 | [21,128,17,96,17,68,65,60], 14 | [12,58,41,132,61,132,96,58], 15 | [44,104,112,52,-20,82,77,136], 16 | [52,48,53,48,54,49,52,48], 17 | [8,175,24,204,60,197,56,74], 18 | [81,72,17,42,105,145,41,120] 19 | ] 20 | const position = [0,0,60,0,60,0,145,0,145,0,210,10,280,-5,350,0,350,0,400,0 ] 21 | const colors = ['#22CAB3','#22CAB3','#22CAB3','#22CAB3','#22CAB3','#22CAB3','#22CAB3','#FF7784','#FF7784','#FF7784'] 22 | const rd = function() { 23 | return -2 + Math.random() * 2 24 | } 25 | 26 | const rdX = function() { 27 | return 10 + Math.floor(Math.random() * (canvas.width - 20 + 1)) 28 | } 29 | 30 | const rdY = function() { 31 | return 10 + Math.floor(Math.random() * (canvas.height - 20 + 1)) 32 | } 33 | 34 | const motionFn = function motion(points, data) { 35 | points.forEach((item, index)=> { 36 | points[index] += data[index] 37 | 38 | 39 | if (index % 2 === 0) { 40 | if (points[index] + this.x < 0) { 41 | points[index] = -this.x 42 | data[index] *= -1 43 | } 44 | 45 | if (points[index] + this.x > canvas.width) { 46 | points[index] = canvas.width - this.x 47 | data[index] *= -1 48 | } 49 | } else { 50 | if (points[index] + this.y < 0) { 51 | points[index] = -this.y 52 | data[index] *= -1 53 | } 54 | 55 | if (points[index] + this.y > canvas.height) { 56 | points[index] = canvas.height - this.y 57 | data[index] *= -1 58 | } 59 | } 60 | }) 61 | } 62 | 63 | 64 | 65 | 66 | 67 | function dance(){ 68 | stage.children.forEach((child ,index)=>{ 69 | child.pointsTo(data[index],2000,{ 70 | end:function(){ 71 | setTimeout(()=>{ 72 | this.play() 73 | },3000) 74 | } 75 | }) 76 | }) 77 | 78 | 79 | } 80 | 81 | 82 | ;(function main(){ 83 | for(let i = 0 ;i < 10; i++) { 84 | 85 | let curve = new Curve({ 86 | x: position[i * 2] + 30, 87 | y: position[i * 2 + 1] + 80, 88 | color: colors[i], 89 | points: [rdX(), rdY(), rdX(), rdY(), rdX(), rdY(), rdX(), rdY()], 90 | motion: motionFn, 91 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 92 | }) 93 | stage.add(curve) 94 | } 95 | 96 | setTimeout(()=>dance(),2000) 97 | setInterval(()=>dance(),10000) 98 | })() 99 | 100 | function tick(){ 101 | stage.update() 102 | requestAnimationFrame(tick) 103 | } 104 | 105 | tick() 106 | -------------------------------------------------------------------------------- /example/rope/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
Try to move your mouse on the canvas.

14 |
The source code is here
15 |
Powered by curvejs
16 | 17 | 18 | -------------------------------------------------------------------------------- /example/rope/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion, SmoothCurve } = curvejs 4 | 5 | class Vector2 { 6 | constructor(x,y) { 7 | this.x = x 8 | this.y = y 9 | } 10 | 11 | sub (b) { return new Vector2(this.x - b.x, this.y - b.y) } 12 | add (b) { return new Vector2(this.x + b.x, this.y + b.y) } 13 | subSelf (a) { this.x -= a.x; this.y -= a.y; return this } 14 | setLength (a) { return this.normalize().multiplyScalar(a) } 15 | multiplyScalar (a) { this.x *= a; this.y *= a; return this } 16 | normalize () { return this.divideScalar(this.length()) } 17 | divideScalar (a) { if (a) { this.x /= a; this.y /= a } else this.set(0, 0); return this } 18 | lengthSq () { return this.x * this.x + this.y * this.y } 19 | length () { return Math.sqrt(this.lengthSq()) } 20 | } 21 | 22 | class Joint { 23 | constructor(segLength, segCount, isFixed, startPoint) { 24 | this.segLength = segLength 25 | this.segCount = segCount 26 | this.isFixed = isFixed 27 | this.startPoint = startPoint 28 | this.points = [] 29 | for (var i = 0; i < this.segCount; i++) { 30 | this.points.push(new Vector2(this.startPoint.x, this.startPoint.y + i * this.segLength)) 31 | } 32 | } 33 | 34 | updatePointsPosition(point, index) { 35 | var tempV = this.points[index - 1].sub(point).setLength(this.segLength) 36 | this.points[index - 1] = point.add(tempV) 37 | if (index > 1) { 38 | this.updatePointsPosition(this.points[index - 1], index - 1) 39 | } else { 40 | if (this.isFixed) { 41 | var v = this.points[0].sub(this.startPoint) 42 | for (var i = 0; i < this.points.length; i++) { 43 | this.points[i].subSelf(v) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | 51 | 52 | let canvas = document.getElementById('myCanvas') 53 | let stage = new Stage(canvas) 54 | 55 | let joint = new Joint(20, 15, false, new Vector2(200, 100)), 56 | points = [] 57 | 58 | joint.points.forEach(v=>{ 59 | points.push(v.x,v.y) 60 | }) 61 | 62 | let curve = new SmoothCurve({ 63 | color:["#754726","#42270C"], 64 | points: points, 65 | disableVision:true, 66 | size:4 67 | }) 68 | 69 | stage.add(curve) 70 | 71 | 72 | 73 | canvas.addEventListener('mousemove',function(evt){ 74 | var rect = canvas.getBoundingClientRect() 75 | joint.points[joint.points.length - 1] = new Vector2(evt.clientX - rect.left, evt.clientY - rect.top) 76 | joint.updatePointsPosition(joint.points[joint.points.length - 1], joint.points.length - 1) 77 | 78 | 79 | joint.points.forEach((v,i)=>{ 80 | points[i*2] = v.x 81 | points[i*2+1]=v.y 82 | }) 83 | },false) 84 | 85 | 86 | 87 | function tick(){ 88 | stage.update() 89 | requestAnimationFrame(tick) 90 | } 91 | 92 | tick() 93 | -------------------------------------------------------------------------------- /example/scale-to/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/scale-to/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | let data = [ 9 | [75,40,75,37,70,25,50,25], 10 | [50,25,20,25,20,62.5,20,62.5], 11 | [20,62.5,20,80,40,102,75,120], 12 | [75,120,110,102,130,80,130,62.5], 13 | [130,62.5,130,62.5,130,25,100,25], 14 | [100,25,85,25,75,37,75,40] 15 | ] 16 | 17 | 18 | function dance(child, scale) { 19 | 20 | child.scaleTo(scale, 2000, { 21 | center: [85,80], 22 | end: function () { 23 | dance(this, scale === 2 ? 0.5 : 2) 24 | } 25 | }) 26 | } 27 | 28 | 29 | ;(function main(){ 30 | for(let i = 0 ;i < 6; i++) { 31 | 32 | let curve = new Curve({ 33 | x:200, 34 | y:100, 35 | color: 'red', 36 | points:data[i] 37 | }) 38 | stage.add(curve) 39 | } 40 | 41 | stage.children.forEach((child)=>{ 42 | dance(child, 2) 43 | }) 44 | 45 | })() 46 | 47 | function tick(){ 48 | stage.update() 49 | requestAnimationFrame(tick) 50 | } 51 | 52 | tick() 53 | -------------------------------------------------------------------------------- /example/simple-es5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 63 | 64 | -------------------------------------------------------------------------------- /example/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/simple/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const rd = function() { 9 | return -2 + Math.random() * 2 10 | } 11 | 12 | let curve = new Curve({ 13 | color: '#00FF00', 14 | points: [277, 327, 230, 314, 236, 326, 257, 326], 15 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 16 | motion: function motion(points, data) { 17 | points.forEach(function (item, index) { 18 | points[index] += data[index] 19 | 20 | if (points[index] < 0) { 21 | points[index] = 0 22 | data[index] *= -1 23 | } 24 | if (index % 2 === 0) { 25 | if (points[index] > canvas.width) { 26 | points[index] = canvas.width 27 | data[index] *= -1 28 | } 29 | } else { 30 | if (points[index] > canvas.height) { 31 | points[index] = canvas.height 32 | data[index] *= -1 33 | } 34 | } 35 | }) 36 | } 37 | }) 38 | window.xxx= curve 39 | stage.add(curve) 40 | 41 | 42 | function tick(){ 43 | stage.update() 44 | requestAnimationFrame(tick) 45 | } 46 | 47 | tick() 48 | -------------------------------------------------------------------------------- /example/siri-wave/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
The source code is here
14 |
Powered by curvejs
15 | 16 | 17 | -------------------------------------------------------------------------------- /example/siri-wave/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | import SiriWave from './siri-wava.js' 3 | const { Stage, SmoothCurve, motion } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const rd = function() { 9 | return -2 + Math.random() * 2 10 | } 11 | 12 | 13 | let curve = new SmoothCurve({ 14 | color: '#4EEE94', 15 | visionMax:10, 16 | visionInterval:10 17 | }) 18 | 19 | stage.add(curve) 20 | 21 | 22 | 23 | var sw = new SiriWave({ 24 | width: 640, 25 | height: 400, 26 | speed:0.4, 27 | noise:50 28 | }); 29 | 30 | var range = document.getElementById('range'); 31 | 32 | 33 | function tick() { 34 | sw.tick() 35 | sw.noise = range.value 36 | curve.points = sw.list.slice(0) 37 | stage.update() 38 | 39 | requestAnimationFrame(tick) 40 | } 41 | 42 | tick() 43 | -------------------------------------------------------------------------------- /example/siri-wave/siri-wava.js: -------------------------------------------------------------------------------- 1 | 2 | export default class SiriWave { 3 | constructor(opt) { 4 | this.opt = opt || {}; 5 | 6 | this.K = 2; 7 | this.F = 6; 8 | this.speed = this.opt.speed || 0.1; 9 | this.noise = this.opt.noise || 0; 10 | this.phase = this.opt.phase || 0; 11 | 12 | this.width = this.opt.width || 320; 13 | this.height = this.opt.height || 100; 14 | 15 | this.list = [] 16 | } 17 | 18 | _globalAttenuationFn(x) { 19 | //http://www.wolframalpha.com/input/?i=pow(2*4%2F(2*4%2Bpow(x,4)),2*2) 20 | return Math.pow(this.K * 4 / (this.K * 4 + Math.pow(x, 4)), this.K * 2); 21 | } 22 | 23 | _drawLine(attenuation) { 24 | var x, y; 25 | this.list.length = 0 26 | for (var i = -this.K; i <= this.K; i += 0.1) { 27 | x = this.width * ((i + this.K) / (this.K * 2)); 28 | y = this.height / 2 + this.noise * this._globalAttenuationFn(i) * (1 / attenuation) * Math.sin(this.F * i - this.phase); 29 | this.list.push( x,y) 30 | } 31 | } 32 | 33 | tick() { 34 | this.phase = (this.phase + this.speed) % (Math.PI * 64); 35 | this._drawLine(1); 36 | } 37 | } -------------------------------------------------------------------------------- /example/smooth/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/smooth/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion, SmoothCurve } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const rd = function() { 9 | return -2 + Math.random() * 2 10 | } 11 | 12 | 13 | let curve = new SmoothCurve({ 14 | color: '#00FF00', 15 | points: [100,100,200,100,300,350,300,250,400,250,300,250,400,250], 16 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 17 | motion: function motion(points, data) { 18 | points.forEach(function (item, index) { 19 | points[index] += data[index] 20 | 21 | if (points[index] < 0) { 22 | points[index] = 0 23 | data[index] *= -1 24 | } 25 | if (index % 2 === 0) { 26 | if (points[index] > canvas.width) { 27 | points[index] = canvas.width 28 | data[index] *= -1 29 | } 30 | } else { 31 | if (points[index] > canvas.height) { 32 | points[index] = canvas.height 33 | data[index] *= -1 34 | } 35 | } 36 | }) 37 | } 38 | }) 39 | 40 | stage.add(curve) 41 | 42 | 43 | function tick(){ 44 | stage.update() 45 | requestAnimationFrame(tick) 46 | } 47 | 48 | tick() 49 | -------------------------------------------------------------------------------- /example/sprout/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
Prees down/move your mouse to create artistic work.
23 | 24 | 25 | -------------------------------------------------------------------------------- /example/sprout/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion, SproutCurve } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | canvas.width = window.innerWidth 7 | canvas.height = window.innerHeight 8 | let stage = new Stage(canvas) 9 | 10 | 11 | let curve = new SproutCurve({ 12 | color: '#65ffff' 13 | }) 14 | 15 | const rect = canvas.getBoundingClientRect() 16 | let currentX = null, 17 | currentY = null, 18 | isMouseDown = false 19 | 20 | canvas.addEventListener('mousedown',(evt)=>{ 21 | isMouseDown = true 22 | currentX = evt.clientX -rect.left 23 | currentY = evt.clientY - rect.top 24 | 25 | },false) 26 | 27 | document.addEventListener('mousemove',(evt)=>{ 28 | if(isMouseDown) { 29 | currentX = evt.clientX - rect.left 30 | currentY = evt.clientY - rect.top 31 | } 32 | evt.preventDefault() 33 | },false) 34 | 35 | 36 | document.addEventListener('mouseup',()=>{ 37 | isMouseDown = false 38 | currentX = null 39 | currentY = null 40 | },false) 41 | 42 | 43 | stage.add(curve) 44 | 45 | curve.addSeed(300,300) 46 | curve.addSeed(300,300) 47 | curve.addSeed(300,300) 48 | curve.addSeed(300,300) 49 | curve.addSeed(300,300) 50 | curve.addSeed(300,300) 51 | 52 | function tick(){ 53 | if(currentX !== null) { 54 | curve.addSeed(currentX, currentY, 1, 1) 55 | } 56 | stage.update(true) 57 | requestAnimationFrame(tick) 58 | } 59 | 60 | tick() 61 | 62 | 63 | setTimeout(()=>{ 64 | 65 | document.getElementById('tip').style.opacity = 0; 66 | },5000) -------------------------------------------------------------------------------- /example/tfc/index-sc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/tfc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/tfc/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | 8 | const rd = function() { 9 | return -2 + Math.random() * 2 10 | } 11 | 12 | var tencent = [2,1,2,1,13,1,13,1,7,1,7,1,7,15,7,15,21,1,21,1,29,1,29,1,21,1,21,1,21,15,21,15,21,15,21,15,29,14,29,14,22,7,22,7,28,7,28,7,38,1,38,1,38,15,38,15,39,1,39,1,49,15,49,15,49,1,49,1,49,15,49,15,70,2,57,0,58,13,69,13,80,1,80,1,87,1,87,1,80,1,80,1,80,14,80,14,80,14,80,14,87,14,87,14,81,7,81,7,87,7,87,7,97,1,97,1,97,14,97,14,98,1,98,1,108,14,108,14,108,1,108,1,108,14,108,14,117,1,117,1,128,1,128,1,123,2,123,2,122,14,122,14] 13 | 14 | let web=[ 147, 1, 147, 1, 151, 14, 151, 14, 151, 14, 151, 14, 157, 1, 157, 1, 157, 1, 157, 1, 161, 14, 161, 14, 161, 14, 161, 14, 165, 1, 165, 1, 176, 1, 176, 1, 183, 1, 183, 1, 175, 1, 175, 1, 175, 14, 175, 14, 176, 14, 176, 14, 183, 14, 183, 14, 176, 7, 176, 7, 182, 7, 182, 7, 193, 1, 193, 1, 193, 14, 193, 14, 193, 1, 201, 0, 201, 6, 193, 7, 193, 7, 201, 7, 201, 14, 192, 14] 15 | 16 | let fe=[ 223,1,223,1,230,1,230,1,223,1,223,1,222,15,222,15,223,8,223,8,230,9,230,9,240,1,240,1,240,15,240,15,240,1,250,1,249,8,240,8,244,8,244,8,249,15,249,15,264,1,254,1,253,13,264,14,264,1,273,1,273,14,264,14,281,1,281,1,281,15,281,15,281,1,281,1,291,14,291,14,291,14,291,14,291,1,291,1,302,1,302,1,311,1,311,1,307,2,307,2,306,15,306,15,319,9,319,9,325,9,325,9,335,1,335,1,335,14,335,14,335,14,335,14,343,14,343,14,336,7,336,7,342,7,342,7,335,1,335,1,343,1,343,1,352,1,352,1,352,14,352,14,353,1,353,1,362,14,362,14,362,14,362,14,363,1,363,1,375,1,375,1,375,14,375,14,375,1,387,0,388,14,375,14 17 | 18 | ] 19 | 20 | let cf = [417,2,402,1,402,14,416,13,432,1,422,0,421,13,432,14,432,14,443,14,442,1,432,1,450,1,450,1,449,15,449,15,450,1,450,1,459,14,459,14,460,1,460,1,459,15,459,15,471,1,471,1,479,1,479,1,471,15,471,15,472,1,472,1,472,8,472,8,478,8,478,8,488,1,488,1,495,2,495,2,488,1,488,1,488,14,488,14,488,14,488,14,495,14,495,14,488,7,488,7,495,7,495,7,505,1,505,1,505,15,505,15,505,1,516,1,516,8,505,8,510,8,510,8,514,14,514,14,525,1,525,1,531,1,531,1,524,1,524,1,524,15,524,15,524,7,524,7,531,7,531,7,525,14,525,14,531,14,531,14,542,1,542,1,541,15,541,15,542,1,542,1,551,14,551,14,552,1,552,1,552,14,552,14,572,2,558,1,559,13,573,13,583,1,583,1,583,14,583,14,583,14,583,14,590,14,590,14,583,7,583,7,589,7,589,7,583,1,583,1,590,1,590,1] 21 | 22 | 23 | function createCurve(arr,color){ 24 | arr.forEach((item,index,self)=>{ 25 | if(index%8===0){ 26 | let curve = new Curve({ 27 | x:10,y:100, 28 | color: color, 29 | points: [self[index],self[1+index],self[2+index],self[3+index],self[4+index],self[5+index],self[6+index],self[7+index]], 30 | data: [rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()], 31 | motion: motion.dance, 32 | data: {angle: 0, r:5 ,step:Math.PI / 100 } 33 | }) 34 | 35 | stage.add(curve) 36 | } 37 | }) 38 | } 39 | 40 | 41 | function tick(){ 42 | stage.update() 43 | requestAnimationFrame(tick) 44 | } 45 | 46 | createCurve(tencent,'#00FF00') 47 | createCurve(web,'#22CAB3') 48 | createCurve(fe,'#FF7784') 49 | createCurve(cf,'#D4B879') 50 | 51 | tick() 52 | -------------------------------------------------------------------------------- /example/water/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlloyTeam/curvejs/b67e22e1f4e6a27ba5817ab9e3cf375f7e5f1f54/example/water/bg.png -------------------------------------------------------------------------------- /example/water/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
The source code is here
14 |
Powered by curvejs
15 | 16 | 17 | -------------------------------------------------------------------------------- /example/water/index.js: -------------------------------------------------------------------------------- 1 | import curvejs from '../../src/index.js' 2 | 3 | const { Stage, Curve, motion, SmoothCurve, perlin } = curvejs 4 | 5 | let canvas = document.getElementById('myCanvas') 6 | let stage = new Stage(canvas) 7 | let bgImg = new Image() 8 | 9 | const rd = function() { 10 | return -2 + Math.random() * 2 11 | } 12 | 13 | 14 | const map = function (value, start, end, valueStart, valueEnd) { 15 | return valueStart + (valueEnd - valueStart) * value / (end - start) 16 | } 17 | 18 | let curve = new SmoothCurve({ 19 | color: '#ffffff', 20 | size: 3, 21 | points: [99,219,110,219,120,222,130,222,137,221,147,221,165,221,175,221,189,224,199,224,216,227,226,227,240,223,250,223,267,224,277,224,290,219], 22 | data: { value:0, step:0.001, interval: 10}, 23 | motion: function motion(points, data) { 24 | points.forEach((item, index)=> { 25 | if (index % 2 === 1 && index !== 1 && index !== points.length - 1) { 26 | points[index] = map(perlin.noise(data.value + data.interval * index), 0, 1, this.copyPoints[index] - 12, this.copyPoints[index] - 5 + 12) 27 | data.value += data.step 28 | } 29 | }) 30 | }, 31 | visionMax:40, 32 | //disableVision:true, 33 | //debug:true, 34 | beforeDraw(ctx){ 35 | ctx.drawImage(bgImg, 0, 0) 36 | }, 37 | afterDraw(ctx){ 38 | //draw water 39 | ctx.save() 40 | ctx.beginPath() 41 | ctx.fillStyle = '#00ffff' 42 | ctx.globalAlpha = 0.7 43 | 44 | var points = this.points 45 | 46 | ctx.beginPath(); 47 | ctx.moveTo(points[0], points[1]); 48 | for (let i = 2, len = points.length; i < len; i += 2) { 49 | if (i === points.length - 4) { 50 | ctx.quadraticCurveTo(points[i], points[i + 1], points[i + 2], points[i + 3]); 51 | } else { 52 | ctx.quadraticCurveTo(points[i], points[i + 1], (points[i] + points[i + 2]) / 2, ((points[i + 1] + points[i + 3]) / 2)); 53 | } 54 | } 55 | 56 | ctx.bezierCurveTo(280, 286, 256, 289,195,286) 57 | 58 | ctx.bezierCurveTo(126, 289, 113, 289, points[0], points[1]) 59 | ctx.fill() 60 | ctx.restore() 61 | 62 | } 63 | }) 64 | 65 | 66 | bgImg.onload = function(){ 67 | stage.add(curve) 68 | } 69 | 70 | bgImg.src = './bg.png' 71 | 72 | function tick(){ 73 | stage.update() 74 | requestAnimationFrame(tick) 75 | } 76 | 77 | tick() -------------------------------------------------------------------------------- /example/word/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | curvejs 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/word/index.js: -------------------------------------------------------------------------------- 1 | 2 | import util from '../../src/util.js' 3 | import curvejs from '../../src/index.js' 4 | 5 | const { Stage, Word, motion } = curvejs 6 | 7 | var lineCount = 10, 8 | random = util.random, 9 | randomColor = util.randomColor, 10 | randomSpeed = util.randomSpeed, 11 | stage = new Stage(800,450,'#container') 12 | 13 | function generatePosition(){ 14 | 15 | stage.add(new Word('c',{ 16 | color: '#22CAB3', 17 | motion: motion.dance, 18 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 19 | 20 | })) 21 | 22 | 23 | stage.add(new Word('u',{ 24 | color: '#22CAB3', 25 | x:60, 26 | motion: motion.dance, 27 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 28 | })) 29 | 30 | stage.add(new Word('r',{ 31 | color: '#22CAB3', 32 | x:145, 33 | motion: motion.dance, 34 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 35 | })) 36 | 37 | stage.add(new Word('v',{ 38 | color: '#22CAB3', 39 | x:210, 40 | y:10, 41 | motion: motion.dance, 42 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 43 | })) 44 | 45 | stage.add(new Word('e',{ 46 | color: '#22CAB3', 47 | x:280, 48 | y:-5, 49 | motion: motion.dance, 50 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 51 | })) 52 | 53 | stage.add(new Word('j',{ 54 | color: '#FF7784', 55 | x:350, 56 | motion: motion.dance, 57 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 58 | })) 59 | 60 | stage.add(new Word('s',{ 61 | color: '#FF7784', 62 | x:400, 63 | motion: motion.dance, 64 | data: {angle: 0, r:5 ,step:Math.PI / 50 } 65 | })) 66 | } 67 | 68 | 69 | function tick(){ 70 | stage.update() 71 | requestAnimationFrame(tick) 72 | } 73 | 74 | ;(function main(){ 75 | generatePosition() 76 | tick() 77 | })() -------------------------------------------------------------------------------- /example/word/main.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var util = { 8 | 9 | random: function random(min, max) { 10 | return min + Math.floor(Math.random() * (max - min + 1)); 11 | }, 12 | 13 | randomColor: function randomColor() { 14 | return ['#22CAB3', '#90CABE', '#A6EFE8', '#C0E9ED', '#C0E9ED', '#DBD4B7', '#D4B879', '#ECCEB2', '#F2ADA6', '#FF7784'][util.random(0, 9)]; 15 | // return '#'+(Math.random()*0xffffff<<0).toString(16); 16 | }, 17 | 18 | randomSpeed: function randomSpeed() { 19 | return (Math.random() > 0.5 ? 1 : -1) * Math.random() * 2; 20 | } 21 | 22 | }; 23 | 24 | (function () { 25 | 'use strict'; 26 | 27 | if (!Date.now) Date.now = function () { 28 | return new Date().getTime(); 29 | }; 30 | 31 | var vendors = ['webkit', 'moz']; 32 | for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 33 | var vp = vendors[i]; 34 | window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 35 | window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; 36 | } 37 | if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy 38 | || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 39 | var lastTime = 0; 40 | window.requestAnimationFrame = function (callback) { 41 | var now = Date.now(); 42 | var nextTime = Math.max(lastTime + 16, now); 43 | return setTimeout(function () { 44 | callback(lastTime = nextTime); 45 | }, nextTime - now); 46 | }; 47 | window.cancelAnimationFrame = clearTimeout; 48 | } 49 | })(); 50 | 51 | /** 52 | * dance motion, spin around. 53 | * 54 | * @param {points} 55 | * @param {data} 56 | * data rule example: 57 | * { angle : 0, r : 5 , step : Math.PI / 50 } 58 | */ 59 | var dance = function (points, data) { 60 | var pre = this.copyPoints, 61 | theta = data.angle, 62 | r = data.r; 63 | 64 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 65 | points[0] = pre[0] + R[0][0] * r; 66 | points[1] = pre[1] + R[1][0] * r; 67 | points[2] = pre[2] + R[0][0] * r; 68 | points[3] = pre[3] + R[1][0] * r; 69 | points[4] = pre[4] + R[0][0] * r; 70 | points[5] = pre[5] + R[1][0] * r; 71 | points[6] = pre[6] + R[0][0] * r; 72 | points[7] = pre[7] + R[1][0] * r; 73 | 74 | data.angle += data.step; 75 | }; 76 | 77 | /** 78 | * move motion. 79 | * 80 | * @param {points} 81 | * @param {data} 82 | * data rule example: 83 | * [1, 0.2, -3, 0.7, 0.5, 0.3, -1, 1] 84 | */ 85 | var move = function (points, data) { 86 | points.forEach(function (item, index) { 87 | points[index] += data[index]; 88 | }); 89 | }; 90 | 91 | /** 92 | * rotate motion. 93 | * 94 | * @param {points} 95 | * @param {data} 96 | * data rule example: 97 | * Math.PI/100 98 | */ 99 | 100 | function _rotate(x1, y1, x2, y2, theta, index, points) { 101 | var v = { x: x1 - x2, y: y1 - y2 }; 102 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 103 | 104 | points[index] = x2 + R[0][0] * v.x + R[0][1] * v.y; 105 | points[index + 1] = y2 + R[1][0] * v.x + R[1][1] * v.y; 106 | } 107 | 108 | function rotate(points, angle) { 109 | var centerX = (points[0] + points[6]) / 2; 110 | var centerY = (points[1] + points[7]) / 2; 111 | _rotate(points[0], points[1], centerX, centerY, angle, 0, points); 112 | _rotate(points[2], points[3], centerX, centerY, angle, 2, points); 113 | _rotate(points[4], points[5], centerX, centerY, angle, 4, points); 114 | _rotate(points[6], points[7], centerX, centerY, angle, 6, points); 115 | } 116 | 117 | function n(x, y) { 118 | var sum = x * x + y * y; 119 | if (sum === 0) return [0, 0]; 120 | var len = Math.sqrt(sum); 121 | return [x / len, y / len]; 122 | } 123 | 124 | function sl(x, y) { 125 | return x * x + y * y; 126 | } 127 | 128 | var vector2 = { 129 | n: n, 130 | sl: sl 131 | }; 132 | 133 | /** 134 | * close or open path motion. 135 | * 136 | * @param {points} 137 | */ 138 | 139 | function open(points) { 140 | var v = vector2.n(points[0] - points[6], points[1] - points[7]); 141 | 142 | points[0] += v[0]; 143 | points[1] += v[1]; 144 | 145 | points[6] -= v[0]; 146 | points[7] -= v[1]; 147 | } 148 | 149 | function close(points) { 150 | var v = vector2.n(points[0] - points[6], points[1] - points[7]); 151 | 152 | points[0] -= v[0]; 153 | points[1] -= v[1]; 154 | 155 | points[6] += v[0]; 156 | points[7] += v[1]; 157 | } 158 | 159 | function auto(points) {} 160 | 161 | var path = { 162 | close: close, 163 | open: open, 164 | auto: auto 165 | }; 166 | 167 | function to(points, target) { 168 | var va1 = [points[2] - points[0], points[3] - points[1]]; 169 | var va2 = [points[4] - points[2], points[5] - points[3]]; 170 | var va3 = [points[6] - points[4], points[7] - points[5]]; 171 | 172 | var vb1 = [target[2] - target[0], target[3] - target[1]]; 173 | var vb2 = [target[4] - target[2], target[5] - target[3]]; 174 | var vb3 = [target[6] - target[4], target[7] - target[5]]; 175 | 176 | var v1 = vector2.n(vb1[0] - va1[0], vb1[1] - va1[1]); 177 | points[0] -= v1[0]; 178 | points[1] -= v1[1]; 179 | 180 | var v2 = vector2.n(vb2[0] - va2[0], vb2[1] - va2[1]); 181 | points[4] += v2[0]; 182 | points[5] += v2[1]; 183 | 184 | var v3 = vector2.n(vb3[0] - va3[0], vb3[1] - va3[1]); 185 | points[6] += v3[0]; 186 | points[7] += v3[1]; 187 | } 188 | 189 | function line(arr) { 190 | 191 | arr[2] += arr[2] > arr[0] ? -1 : 1; 192 | arr[3] += arr[3] > arr[1] ? -1 : 1; 193 | arr[4] += arr[4] > arr[6] ? -1 : 1; 194 | arr[5] += arr[5] > arr[7] ? -1 : 1; 195 | } 196 | 197 | function circle(points, data) { 198 | var R = [[Math.cos(data.angle), -Math.sin(data.angle)], [Math.sin(data.angle), Math.cos(data.angle)]]; 199 | 200 | this.data.angle += data.step; 201 | 202 | points[0] = points[0] + R[0][0]; 203 | points[1] = points[1] + R[1][0]; 204 | points[6] = points[6] + R[0][0]; 205 | points[7] = points[7] + R[1][0]; 206 | } 207 | 208 | var motion$1 = { 209 | dance: dance, 210 | move: move, 211 | rotate: rotate, 212 | to: to, 213 | line: line, 214 | circle: circle, 215 | close: path.close, 216 | open: path.open 217 | }; 218 | 219 | var classCallCheck = function (instance, Constructor) { 220 | if (!(instance instanceof Constructor)) { 221 | throw new TypeError("Cannot call a class as a function"); 222 | } 223 | }; 224 | 225 | var createClass = function () { 226 | function defineProperties(target, props) { 227 | for (var i = 0; i < props.length; i++) { 228 | var descriptor = props[i]; 229 | descriptor.enumerable = descriptor.enumerable || false; 230 | descriptor.configurable = true; 231 | if ("value" in descriptor) descriptor.writable = true; 232 | Object.defineProperty(target, descriptor.key, descriptor); 233 | } 234 | } 235 | 236 | return function (Constructor, protoProps, staticProps) { 237 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 238 | if (staticProps) defineProperties(Constructor, staticProps); 239 | return Constructor; 240 | }; 241 | }(); 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | var inherits = function (subClass, superClass) { 252 | if (typeof superClass !== "function" && superClass !== null) { 253 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 254 | } 255 | 256 | subClass.prototype = Object.create(superClass && superClass.prototype, { 257 | constructor: { 258 | value: subClass, 259 | enumerable: false, 260 | writable: true, 261 | configurable: true 262 | } 263 | }); 264 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 265 | }; 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | var possibleConstructorReturn = function (self, call) { 278 | if (!self) { 279 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 280 | } 281 | 282 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 283 | }; 284 | 285 | var Curve = function () { 286 | function Curve(option) { 287 | classCallCheck(this, Curve); 288 | 289 | this.points = option.points || []; 290 | this.color = option.color || 'black'; 291 | this.x = option.x || 0; 292 | this.y = option.y || 0; 293 | this.vision = option.vision || []; 294 | this.visionMax = 640; 295 | this.visionInterval = option.visionInterval || 10; 296 | 297 | this._preDate = Date.now(); 298 | this._now = new Date(); 299 | 300 | this.data = option.data; 301 | 302 | this.copyPoints = this.points.slice(0); 303 | this.motion = option.motion || motion$1.dance; 304 | 305 | this._initVision(option.visionCount || 80); 306 | } 307 | 308 | createClass(Curve, [{ 309 | key: '_initVision', 310 | value: function _initVision() { 311 | var i = 0; 312 | for (; i < 80; i++) { 313 | this.tick(true); 314 | } 315 | } 316 | }, { 317 | key: 'tick', 318 | value: function tick(tickSelf) { 319 | this._now = Date.now(); 320 | if (this._now - this._preDate > this.visionInterval || tickSelf) { 321 | 322 | this.vision.push.apply(this.vision, this.points); 323 | if (this.vision.length > this.visionMax) { 324 | this.vision.splice(0, 8); 325 | } 326 | this._preDate = this._now; 327 | } 328 | 329 | this.motion.call(this, this.points, this.data); 330 | } 331 | }, { 332 | key: 'draw', 333 | value: function draw(ctx) { 334 | this.tick(); 335 | ctx.save(); 336 | ctx.translate(this.x, this.y); 337 | ctx.globalAlpha = 1; 338 | var points = this.points; 339 | ctx.beginPath(); 340 | ctx.strokeStyle = this.color; 341 | ctx.moveTo.call(ctx, points[0], points[1]); 342 | ctx.bezierCurveTo.call(ctx, points[2], points[3], points[4], points[5], points[6], points[7]); 343 | ctx.stroke(); 344 | 345 | var vision = this.vision; 346 | 347 | var i = 0, 348 | len = vision.length; 349 | for (; i < len; i += 8) { 350 | ctx.globalAlpha = i / this.visionMax * 0.1; 351 | ctx.beginPath(); 352 | ctx.strokeStyle = this.color; 353 | ctx.moveTo.call(ctx, vision[i], vision[i + 1]); 354 | ctx.bezierCurveTo.call(ctx, vision[i + 2], vision[i + 3], vision[i + 4], vision[i + 5], vision[i + 6], vision[i + 7]); 355 | ctx.stroke(); 356 | } 357 | ctx.restore(); 358 | } 359 | }, { 360 | key: 'clone', 361 | value: function clone() {} 362 | }]); 363 | return Curve; 364 | }(); 365 | 366 | var Group = function () { 367 | function Group() { 368 | classCallCheck(this, Group); 369 | 370 | this.children = []; 371 | } 372 | 373 | createClass(Group, [{ 374 | key: "add", 375 | value: function add(line) { 376 | this.children.push(line); 377 | } 378 | }, { 379 | key: "remove", 380 | value: function remove(line) { 381 | var i = 0, 382 | len = this.children.length; 383 | for (; i < len; i++) { 384 | 385 | if (line === this.children[i]) { 386 | 387 | this.children.splice(i, 1); 388 | break; 389 | } 390 | } 391 | } 392 | }, { 393 | key: "draw", 394 | value: function draw(ctx) { 395 | this.children.forEach(function (child) { 396 | child.draw(ctx); 397 | }); 398 | } 399 | }]); 400 | return Group; 401 | }(); 402 | 403 | var Stage$1 = function (_Group) { 404 | inherits(Stage, _Group); 405 | 406 | function Stage(width, height, renderTo) { 407 | classCallCheck(this, Stage); 408 | 409 | var _this = possibleConstructorReturn(this, (Stage.__proto__ || Object.getPrototypeOf(Stage)).call(this)); 410 | 411 | if (arguments.length === 1) { 412 | _this.canvas = width; 413 | _this.ctx = _this.canvas.getContext('2d'); 414 | _this.width = _this.canvas.width; 415 | _this.height = _this.canvas.height; 416 | } else if (arguments.length === 3) { 417 | _this.canvas = document.createElement('canvas'); 418 | _this.canvas.width = width; 419 | _this.canvas.height = height; 420 | _this.width = width; 421 | _this.height = height; 422 | _this.ctx = _this.canvas.getContext('2d'); 423 | document.querySelector(renderTo).appendChild(_this.canvas); 424 | } 425 | 426 | return _this; 427 | } 428 | 429 | createClass(Stage, [{ 430 | key: 'addExistLine', 431 | value: function addExistLine(process) {} 432 | }, { 433 | key: 'sameAs', 434 | value: function sameAs(index) {} 435 | }, { 436 | key: 'update', 437 | value: function update() { 438 | var _this2 = this; 439 | 440 | this.ctx.clearRect(0, 0, this.width, this.height); 441 | this.children.forEach(function (child) { 442 | child.draw(_this2.ctx); 443 | }); 444 | } 445 | }]); 446 | return Stage; 447 | }(Group); 448 | 449 | /** 450 | * Created by dntzhang on 2017/4/15. 451 | */ 452 | var data = { 453 | 454 | 'c': [[70, 70, 12, 76, 12, 123, 70, 128]], 455 | 'u': [[25, 68, 23, 140, 80, 140, 74, 100], [73, 62, 72, 90, 75, 110, 93, 134]], 456 | 'r': [[21, 62, 21, 62, 21, 128, 21, 128], [21, 128, 17, 96, 17, 68, 65, 60]], 457 | 'v': [[12, 58, 41, 132, 61, 132, 96, 58]], 458 | 'j': [[52, 48, 53, 48, 54, 49, 52, 48], [8, 175, 24, 204, 60, 197, 56, 74]], 459 | 'e': [[44, 104, 112, 52, -20, 82, 77, 136]], 's': [[81, 72, 17, 42, 105, 145, 41, 120]], 460 | 'l': [[77, 27, 30, 114, 36, 126, 57, 126]], 461 | 'i': [[56, 71, 56, 72, 59, 70, 56, 73], [47, 93, 59, 81, 47, 121, 59, 111]] 462 | 463 | }; 464 | 465 | var Word$1 = function (_Group) { 466 | inherits(Word, _Group); 467 | 468 | function Word(word, option) { 469 | classCallCheck(this, Word); 470 | 471 | var _this = possibleConstructorReturn(this, (Word.__proto__ || Object.getPrototypeOf(Word)).call(this)); 472 | 473 | option = Object.assign({ 474 | x: 0, y: 0, color: 'black' 475 | }, option); 476 | 477 | _this.x = option.x; 478 | _this.y = option.y; 479 | _this.color = option.color; 480 | _this.word = word; 481 | _this.data = option.data; 482 | _this.motion = option.motion; 483 | 484 | _this.points = data[word]; 485 | 486 | _this._init(); 487 | return _this; 488 | } 489 | 490 | createClass(Word, [{ 491 | key: '_init', 492 | value: function _init() { 493 | var _this2 = this; 494 | 495 | this.points.forEach(function (item) { 496 | 497 | _this2.add(new Curve({ 498 | x: _this2.x, 499 | y: _this2.y, 500 | points: item, 501 | color: _this2.color, 502 | data: _this2.data, 503 | motion: _this2.motion 504 | })); 505 | }); 506 | } 507 | }]); 508 | return Word; 509 | }(Group); 510 | 511 | var curvejs = { 512 | Curve: Curve, 513 | Group: Group, 514 | Stage: Stage$1, 515 | motion: motion$1, 516 | Word: Word$1 517 | }; 518 | 519 | var Stage = curvejs.Stage; 520 | var Word = curvejs.Word; 521 | var motion = curvejs.motion; 522 | 523 | 524 | var lineCount = 10; 525 | var random$1 = util.random; 526 | var randomColor$1 = util.randomColor; 527 | var randomSpeed$1 = util.randomSpeed; 528 | var stage = new Stage(800, 450, '#container'); 529 | 530 | function generatePosition() { 531 | 532 | stage.add(new Word('c', { 533 | color: '#22CAB3', 534 | motion: motion.dance, 535 | data: { angle: 0, r: 5, step: Math.PI / 50 } 536 | 537 | })); 538 | 539 | stage.add(new Word('u', { 540 | color: '#22CAB3', 541 | x: 60, 542 | motion: motion.dance, 543 | data: { angle: 0, r: 5, step: Math.PI / 50 } 544 | })); 545 | 546 | stage.add(new Word('r', { 547 | color: '#22CAB3', 548 | x: 145, 549 | motion: motion.dance, 550 | data: { angle: 0, r: 5, step: Math.PI / 50 } 551 | })); 552 | 553 | stage.add(new Word('v', { 554 | color: '#22CAB3', 555 | x: 210, 556 | y: 10, 557 | motion: motion.dance, 558 | data: { angle: 0, r: 5, step: Math.PI / 50 } 559 | })); 560 | 561 | stage.add(new Word('e', { 562 | color: '#22CAB3', 563 | x: 280, 564 | y: -5, 565 | motion: motion.dance, 566 | data: { angle: 0, r: 5, step: Math.PI / 50 } 567 | })); 568 | 569 | stage.add(new Word('j', { 570 | color: '#FF7784', 571 | x: 350, 572 | motion: motion.dance, 573 | data: { angle: 0, r: 5, step: Math.PI / 50 } 574 | })); 575 | 576 | stage.add(new Word('s', { 577 | color: '#FF7784', 578 | x: 400, 579 | motion: motion.dance, 580 | data: { angle: 0, r: 5, step: Math.PI / 50 } 581 | })); 582 | } 583 | 584 | function tick$1() { 585 | stage.update(); 586 | requestAnimationFrame(tick$1); 587 | } 588 | 589 | (function main() { 590 | generatePosition(); 591 | tick$1(); 592 | })(); 593 | 594 | }))); 595 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | curvejs 7 | 8 | 24 | 25 | 196 | 197 | 198 | 199 |
200 |
201 |

202 | Made curve a dancer in HTML5 canvas. 203 |

204 | 224 |

225 | v0.1.0 © 2017 AlloyTeam.com 226 |

227 |
228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curvejs", 3 | "version": "0.3.3", 4 | "description": "Curve is a dancer.", 5 | "main": "dist/curve.js", 6 | "scripts": { 7 | "dev": "rollup -c", 8 | "dist": "rollup -c", 9 | "build": "npm run dev&&npm run dist", 10 | "simple": "rollup -c -w", 11 | "word": "rollup -c -w", 12 | "rope": "rollup -c -w", 13 | "water": "rollup -c -w", 14 | "smooth": "rollup -c -w", 15 | "sprout": "rollup -c -w", 16 | "noise": "rollup -c -w", 17 | "siri-wave": "rollup -c -w", 18 | "points-to":"rollup -c -w", 19 | "curves": "rollup -c -w", 20 | "scale-to": "rollup -c -w", 21 | "watch": "rollup -c -w" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/AlloyTeam/curvejs.git" 26 | }, 27 | "keywords": [ 28 | "curvejs", 29 | "bezier", 30 | "curve", 31 | "canvas", 32 | "animation", 33 | "dance" 34 | ], 35 | "author": "dntzhang", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/AlloyTeam/curvejs/issues" 39 | }, 40 | "files": [ 41 | "lib", 42 | "dist" 43 | ], 44 | "homepage": "https://github.com/AlloyTeam/curvejs", 45 | "dependencies": {}, 46 | "devDependencies": { 47 | "babel-plugin-external-helpers": "^6.18.0", 48 | "babel-preset-es2015": "^6.18.0", 49 | "babelrc-rollup": "^3.0.0", 50 | "rollup": "^0.37.0", 51 | "rollup-plugin-babel": "^2.7.1", 52 | "rollup-plugin-license": "^0.3.0", 53 | "rollup-plugin-uglify": "^1.0.1", 54 | "rollup-watch": "^2.5.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Curvejs Playground 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | 22 |
23 |
24 | 104 |
105 |
106 | 107 |
108 |

109 |     
110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /pg/preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 26 | 27 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import babelrc from 'babelrc-rollup'; 3 | import uglify from 'rollup-plugin-uglify'; 4 | 5 | const ENV = process.env.npm_lifecycle_event; 6 | const license = require('rollup-plugin-license'); 7 | 8 | let pkg = require('./package.json'); 9 | let external = Object.keys(pkg.dependencies); 10 | let licensePlugin = license({ 11 | banner: " curvejs v"+pkg.version+" By dntzhang \r\n Github: https://github.com/AlloyTeam/curvejs\r\n MIT Licensed." 12 | }) 13 | 14 | let config = { 15 | entry: 'src/index.js', 16 | format:'umd', 17 | moduleName:'curvejs', 18 | plugins: [ 19 | babel(babelrc()) 20 | ], 21 | external: external 22 | } 23 | 24 | if(ENV === 'dist'){ 25 | config.plugins.push(uglify(),licensePlugin) 26 | config.dest = 'dist/curve.min.js' 27 | }else if(ENV==='dev'){ 28 | config.plugins.push(licensePlugin) 29 | config.dest = 'dist/curve.js' 30 | }else{ 31 | config.entry = 'example/'+ENV+'/index.js' 32 | config.dest = 'example/'+ENV+'/main.js' 33 | } 34 | 35 | export default config -------------------------------------------------------------------------------- /src/color.js: -------------------------------------------------------------------------------- 1 | let cache = {} 2 | 3 | const cssColors = { 4 | aliceblue: 0xF0F8FF, 5 | antiquewhite: 0xFAEBD7, 6 | aqua: 0x00FFFF, 7 | aquamarine: 0x7FFFD4, 8 | azure: 0xF0FFFF, 9 | beige: 0xF5F5DC, 10 | bisque: 0xFFE4C4, 11 | black: 0x000000, 12 | blanchedalmond: 0xFFEBCD, 13 | blue: 0x0000FF, 14 | blueviolet: 0x8A2BE2, 15 | brown: 0xA52A2A, 16 | burlywood: 0xDEB887, 17 | cadetblue: 0x5F9EA0, 18 | chartreuse: 0x7FFF00, 19 | chocolate: 0xD2691E, 20 | coral: 0xFF7F50, 21 | cornflowerblue: 0x6495ED, 22 | cornsilk: 0xFFF8DC, 23 | crimson: 0xDC143C, 24 | cyan: 0x00FFFF, 25 | darkblue: 0x00008B, 26 | darkcyan: 0x008B8B, 27 | darkgoldenrod: 0xB8860B, 28 | darkgray: 0xA9A9A9, 29 | darkgrey: 0xA9A9A9, 30 | darkgreen: 0x006400, 31 | darkkhaki: 0xBDB76B, 32 | darkmagenta: 0x8B008B, 33 | darkolivegreen: 0x556B2F, 34 | darkorange: 0xFF8C00, 35 | darkorchid: 0x9932CC, 36 | darkred: 0x8B0000, 37 | darksalmon: 0xE9967A, 38 | darkseagreen: 0x8FBC8F, 39 | darkslateblue: 0x483D8B, 40 | darkslategray: 0x2F4F4F, 41 | darkslategrey: 0x2F4F4F, 42 | darkturquoise: 0x00CED1, 43 | darkviolet: 0x9400D3, 44 | deeppink: 0xFF1493, 45 | deepskyblue: 0x00BFFF, 46 | dimgray: 0x696969, 47 | dimgrey: 0x696969, 48 | dodgerblue: 0x1E90FF, 49 | firebrick: 0xB22222, 50 | floralwhite: 0xFFFAF0, 51 | forestgreen: 0x228B22, 52 | fuchsia: 0xFF00FF, 53 | gainsboro: 0xDCDCDC, 54 | ghostwhite: 0xF8F8FF, 55 | gold: 0xFFD700, 56 | goldenrod: 0xDAA520, 57 | gray: 0x808080, 58 | grey: 0x808080, 59 | green: 0x008000, 60 | greenyellow: 0xADFF2F, 61 | honeydew: 0xF0FFF0, 62 | hotpink: 0xFF69B4, 63 | indianred: 0xCD5C5C, 64 | indigo: 0x4B0082, 65 | ivory: 0xFFFFF0, 66 | khaki: 0xF0E68C, 67 | lavender: 0xE6E6FA, 68 | lavenderblush: 0xFFF0F5, 69 | lawngreen: 0x7CFC00, 70 | lemonchiffon: 0xFFFACD, 71 | lightblue: 0xADD8E6, 72 | lightcoral: 0xF08080, 73 | lightcyan: 0xE0FFFF, 74 | lightgoldenrodyellow: 0xFAFAD2, 75 | lightgray: 0xD3D3D3, 76 | lightgrey: 0xD3D3D3, 77 | lightgreen: 0x90EE90, 78 | lightpink: 0xFFB6C1, 79 | lightsalmon: 0xFFA07A, 80 | lightseagreen: 0x20B2AA, 81 | lightskyblue: 0x87CEFA, 82 | lightslategray: 0x778899, 83 | lightslategrey: 0x778899, 84 | lightsteelblue: 0xB0C4DE, 85 | lightyellow: 0xFFFFE0, 86 | lime: 0x00FF00, 87 | limegreen: 0x32CD32, 88 | linen: 0xFAF0E6, 89 | magenta: 0xFF00FF, 90 | maroon: 0x800000, 91 | mediumaquamarine: 0x66CDAA, 92 | mediumblue: 0x0000CD, 93 | mediumorchid: 0xBA55D3, 94 | mediumpurple: 0x9370D8, 95 | mediumseagreen: 0x3CB371, 96 | mediumslateblue: 0x7B68EE, 97 | mediumspringgreen: 0x00FA9A, 98 | mediumturquoise: 0x48D1CC, 99 | mediumvioletred: 0xC71585, 100 | midnightblue: 0x191970, 101 | mintcream: 0xF5FFFA, 102 | mistyrose: 0xFFE4E1, 103 | moccasin: 0xFFE4B5, 104 | navajowhite: 0xFFDEAD, 105 | navy: 0x000080, 106 | oldlace: 0xFDF5E6, 107 | olive: 0x808000, 108 | olivedrab: 0x6B8E23, 109 | orange: 0xFFA500, 110 | orangered: 0xFF4500, 111 | orchid: 0xDA70D6, 112 | palegoldenrod: 0xEEE8AA, 113 | palegreen: 0x98FB98, 114 | paleturquoise: 0xAFEEEE, 115 | palevioletred: 0xD87093, 116 | papayawhip: 0xFFEFD5, 117 | peachpuff: 0xFFDAB9, 118 | peru: 0xCD853F, 119 | pink: 0xFFC0CB, 120 | plum: 0xDDA0DD, 121 | powderblue: 0xB0E0E6, 122 | purple: 0x800080, 123 | red: 0xFF0000, 124 | rosybrown: 0xBC8F8F, 125 | royalblue: 0x4169E1, 126 | saddlebrown: 0x8B4513, 127 | salmon: 0xFA8072, 128 | sandybrown: 0xF4A460, 129 | seagreen: 0x2E8B57, 130 | seashell: 0xFFF5EE, 131 | sienna: 0xA0522D, 132 | silver: 0xC0C0C0, 133 | skyblue: 0x87CEEB, 134 | slateblue: 0x6A5ACD, 135 | slategray: 0x708090, 136 | slategrey: 0x708090, 137 | snow: 0xFFFAFA, 138 | springgreen: 0x00FF7F, 139 | steelblue: 0x4682B4, 140 | tan: 0xD2B48C, 141 | teal: 0x008080, 142 | thistle: 0xD8BFD8, 143 | tomato: 0xFF6347, 144 | turquoise: 0x40E0D0, 145 | violet: 0xEE82EE, 146 | wheat: 0xF5DEB3, 147 | white: 0xFFFFFF, 148 | whitesmoke: 0xF5F5F5, 149 | yellow: 0xFFFF00, 150 | yellowgreen: 0x9ACD32 151 | } 152 | 153 | function lerp(start, end, percent){ 154 | return makeGradientColor(hexToRgb(start),hexToRgb(end),percent) 155 | } 156 | 157 | var hexTriplet = ("01".substr(-1) === "1" ? 158 | // pad 6 zeros to the left 159 | function (cssColor) { 160 | return "#" + ("00000" + cssColor.toString(16)).substr(-6); 161 | } 162 | : // IE doesn't support substr with negative numbers 163 | function (cssColor) { 164 | var str = cssColor.toString(16); 165 | return "#" + (new Array( str.length < 6 ? 6 - str.length + 1 : 0)).join("0") + str; 166 | } 167 | ); 168 | function hexToRgb(hex) { 169 | if (cache[hex]) return cache[hex] 170 | let cssColor = null 171 | if (cssColors[hex]) { 172 | cssColor = hex 173 | hex = hexTriplet(cssColors[hex]) 174 | } 175 | 176 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 177 | let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i 178 | hex = hex.replace(shorthandRegex, function (m, r, g, b) { 179 | return r + r + g + g + b + b 180 | }) 181 | 182 | let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) 183 | let key = cssColor ? cssColor : hex 184 | cache[key] = result ? { 185 | r: parseInt(result[1], 16), 186 | g: parseInt(result[2], 16), 187 | b: parseInt(result[3], 16) 188 | } : null 189 | return cache[key] 190 | } 191 | 192 | function makeGradientColor(color1, color2, percent) { 193 | var newColor = {}; 194 | 195 | if (percent < 0)percent = 0; 196 | if (percent > 100)percent = 100; 197 | function makeChannel(a, b) { 198 | return (a + Math.round((b - a) * (percent / 100))); 199 | } 200 | 201 | function makeColorPiece(num) { 202 | num = Math.min(num, 255); // not more than 255 203 | num = Math.max(num, 0); // not less than 0 204 | var str = num.toString(16); 205 | if (str.length < 2) { 206 | str = "0" + str; 207 | } 208 | return (str); 209 | } 210 | 211 | newColor.r = makeChannel(color1.r, color2.r); 212 | newColor.g = makeChannel(color1.g, color2.g); 213 | newColor.b = makeChannel(color1.b, color2.b); 214 | newColor.cssColor = "#" + 215 | makeColorPiece(newColor.r) + 216 | makeColorPiece(newColor.g) + 217 | makeColorPiece(newColor.b); 218 | return newColor.cssColor 219 | } 220 | 221 | export default { 222 | lerp, 223 | hexToRgb, 224 | makeGradientColor 225 | } -------------------------------------------------------------------------------- /src/curve.js: -------------------------------------------------------------------------------- 1 | 2 | class Curve { 3 | constructor(option) { 4 | this.points = option.points || [0,0,0,0,0,0,0,0] 5 | this.color = option.color || 'black' 6 | this.x = option.x || 0 7 | this.y = option.y || 0 8 | this.vision = option.vision || [] 9 | this.visionMax = 720 10 | this.visionInterval = option.visionInterval || 10 11 | 12 | this._preDate = Date.now() 13 | this._now = new Date() 14 | 15 | this.data = option.data 16 | 17 | this._noop = function (){} 18 | this._ease = function (value){ return value } 19 | this._targetPoints = null 20 | 21 | this.copyPoints = this.points.slice(0) 22 | this.motion = option.motion|| this._noop 23 | 24 | this.visionAlpha = option.visionAlpha === void 0 ? 0.2 :option.visionAlpha 25 | 26 | if(option.initVision === void 0 || option.initVision ) { 27 | this._initVision(option.visionCount || 80) 28 | } 29 | } 30 | 31 | _initVision() { 32 | var i = 0; 33 | for (; i < 80; i++) { 34 | this.tick(true) 35 | } 36 | } 37 | 38 | tick(tickSelf) { 39 | this._now = Date.now() 40 | if (this._now - this._preDate > this.visionInterval || tickSelf) { 41 | 42 | this.vision.push.apply(this.vision, this.points) 43 | this.vision.push(this.color) 44 | 45 | if (this.vision.length > this.visionMax) { 46 | this.vision.splice(0, 9) 47 | } 48 | this._preDate = this._now 49 | } 50 | 51 | if(!this.pauseMotion) { 52 | this.motion.call(this, this.points, this.data) 53 | } 54 | 55 | if(this._targetPoints){ 56 | this._pointsTo() 57 | } 58 | } 59 | 60 | pause(){ 61 | this.pauseMotion = true 62 | } 63 | 64 | play(){ 65 | this.pauseMotion = false 66 | } 67 | 68 | draw(ctx) { 69 | this.tick() 70 | ctx.save() 71 | ctx.translate(this.x, this.y) 72 | ctx.globalAlpha = 1 73 | ctx.strokeStyle = this.color 74 | var points = this.points 75 | ctx.beginPath() 76 | ctx.moveTo.call(ctx, points[0], points[1]) 77 | ctx.bezierCurveTo.call(ctx, points[2], points[3], points[4], points[5], points[6], points[7]) 78 | ctx.stroke() 79 | 80 | var vision = this.vision 81 | 82 | var i = 0, len = vision.length 83 | for (; i < len; i += 9) { 84 | ctx.globalAlpha = i / this.visionMax * this.visionAlpha 85 | ctx.strokeStyle = vision[i + 8] 86 | ctx.beginPath() 87 | ctx.moveTo.call(ctx, vision[i], vision[i + 1]) 88 | ctx.bezierCurveTo.call(ctx, vision[i + 2], vision[i + 3], vision[i + 4], vision[i + 5], vision[i + 6], vision[i + 7]) 89 | ctx.stroke() 90 | 91 | } 92 | ctx.restore() 93 | } 94 | 95 | pointsTo(points, time, option){ 96 | this.pause() 97 | let ps = this.points 98 | this._targetPoints = points 99 | this._pto = option 100 | this._ptStart = option.start || this._noop 101 | this._ptProgress = option.progress || this._noop 102 | this._ptEnd = option.end || this._noop 103 | this._ptEase = option.ease || this._ease 104 | this._ptTime = time 105 | this._ptStartTime = Date.now() 106 | this._ptCopyPoints = ps.slice(0) 107 | 108 | this._ptDistance = [points[0] - ps[0],points[1] - ps[1],points[2] - ps[2],points[3] - ps[3],points[4] - ps[4],points[5] - ps[5],points[6] - ps[6],points[7] - ps[7]] 109 | 110 | this._ptStart.call(this, 0) 111 | } 112 | 113 | translatePoints(xy, time, option) { 114 | 115 | xy = Object.assign({x: 0, y: 0}, xy) 116 | let ps = this.points 117 | this.pointsTo([ps[0] + xy.x, ps[1] + xy.y, ps[2] + xy.x, ps[3] + xy.y, ps[4] + xy.x, ps[5] + xy.y, ps[6] + xy.x, ps[7] + xy.y], time, option) 118 | 119 | } 120 | 121 | scaleTo(scale, time, option) { 122 | let scaleX = 1, 123 | scaleY = 1 124 | if (typeof scale === 'number') { 125 | scaleX = scaleY = scale 126 | } else { 127 | (scale.scaleX !== void 0) && (scaleX = scale.scaleX) 128 | (scale.scaleY !== void 0) && (scaleY = scale.scaleX) 129 | } 130 | 131 | let points = this.points 132 | let centerX = (option.center !== void 0) ? option.center[0] : (points[0] + points[6]) / 2 133 | let centerY = (option.center !== void 0) ? option.center[1] : (points[1] + points[7]) / 2 134 | 135 | 136 | this.pointsTo( 137 | [scaleX * (points[0] - centerX) + centerX, 138 | scaleY * (points[1] - centerY) + centerY, 139 | scaleX * (points[2] - centerX) + centerX, 140 | scaleY * (points[3] - centerY) + centerY, 141 | scaleX * (points[4] - centerX) + centerX, 142 | scaleY * (points[5] - centerY) + centerY, 143 | scaleX * (points[6] - centerX) + centerX, 144 | scaleY * (points[7] - centerY) + centerY], time, option) 145 | 146 | } 147 | 148 | rotateTo(rotation, time, option) { 149 | 150 | 151 | } 152 | 153 | 154 | _pointsTo(){ 155 | 156 | let ps = this.points 157 | let dt = Date.now() - this._ptStartTime 158 | if(dt < this._ptTime) { 159 | let progress = dt / this._ptTime 160 | ps.forEach((v,i)=>{ 161 | ps[i] =this._ptCopyPoints[i] + this._ptDistance[i] * this._ptEase(progress) 162 | }) 163 | this._ptProgress.call(this, progress) 164 | }else{ 165 | ps = this._targetPoints.slice(0) 166 | this._targetPoints = null 167 | this._ptEnd.call(this, 1) 168 | } 169 | } 170 | 171 | colorTo(){ 172 | 173 | } 174 | 175 | clone() { 176 | } 177 | } 178 | 179 | export default Curve -------------------------------------------------------------------------------- /src/group.js: -------------------------------------------------------------------------------- 1 | class Group { 2 | constructor() { 3 | this.children = [] 4 | } 5 | 6 | add(line) { 7 | this.children.push(line) 8 | } 9 | 10 | remove(line) { 11 | var i = 0, 12 | len = this.children.length 13 | for (; i < len; i++) { 14 | 15 | if (line === this.children[i]) { 16 | 17 | this.children.splice(i, 1) 18 | break 19 | } 20 | } 21 | } 22 | 23 | draw(ctx){ 24 | this.children.forEach(function(child) { 25 | child.draw(ctx) 26 | }) 27 | } 28 | } 29 | 30 | export default Group -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './raf.js' 2 | import Curve from './curve.js' 3 | import Group from './group.js' 4 | import Stage from './stage.js' 5 | import motion from './motion/index.js' 6 | import perlin from './noise.js' 7 | import Word from './word.js' 8 | import color from './color.js' 9 | import SmoothCurve from './smooth-curve.js' 10 | import SproutCurve from './sprout-curve.js' 11 | 12 | export default { 13 | Curve, 14 | Group, 15 | Stage, 16 | motion, 17 | Word, 18 | perlin, 19 | color, 20 | SmoothCurve, 21 | SproutCurve 22 | } -------------------------------------------------------------------------------- /src/motion/circle.js: -------------------------------------------------------------------------------- 1 | export default function circle(points, data){ 2 | var R = [[Math.cos(data.angle), -Math.sin(data.angle)], [Math.sin(data.angle), Math.cos(data.angle)]]; 3 | 4 | this.data.angle += data.step 5 | 6 | points[0] = points[0] + R[0][0] 7 | points[1] = points[1] + R[1][0] 8 | points[6] = points[6] + R[0][0] 9 | points[7] = points[7] + R[1][0] 10 | } -------------------------------------------------------------------------------- /src/motion/dance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dance motion, spin around. 3 | * 4 | * @param {points} 5 | * @param {data} 6 | * data rule example: 7 | * { angle : 0, r : 5 , step : Math.PI / 50 } 8 | */ 9 | export default function (points, data) { 10 | var pre = this.copyPoints, 11 | theta = data.angle, 12 | r = data.r 13 | 14 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 15 | points[0] = pre[0] + R[0][0] * r 16 | points[1] = pre[1] + R[1][0] * r 17 | points[2] = pre[2] + R[0][0] * r 18 | points[3] = pre[3] + R[1][0] * r 19 | points[4] = pre[4] + R[0][0] * r 20 | points[5] = pre[5] + R[1][0] * r 21 | points[6] = pre[6] + R[0][0] * r 22 | points[7] = pre[7] + R[1][0] * r 23 | 24 | data.angle += data.step 25 | } -------------------------------------------------------------------------------- /src/motion/expand.js: -------------------------------------------------------------------------------- 1 | function sliceBezier(p1,cp1,cp2,p2, t){ 2 | var x1= p1.x, 3 | y1=p1.y, 4 | x2= cp1.x, 5 | y2=cp1.y, 6 | x3= cp2.x, 7 | y3=cp2.y, 8 | x4= p2.x, 9 | y4=p2.y; 10 | 11 | 12 | var x12 = (x2-x1)*t+x1 13 | var y12 = (y2-y1)*t+y1 14 | 15 | var x23 = (x3-x2)*t+x2 16 | var y23 = (y3-y2)*t+y2 17 | 18 | var x34 = (x4-x3)*t+x3 19 | var y34 = (y4-y3)*t+y3 20 | 21 | var x123 = (x23-x12)*t+x12 22 | var y123 = (y23-y12)*t+y12 23 | 24 | var x234 = (x34-x23)*t+x23 25 | var y234 = (y34-y23)*t+y23 26 | 27 | var x1234 = (x234-x123)*t+x123 28 | var y1234 = (y234-y123)*t+y123 29 | 30 | return [x1, y1, x12, y12, x123, y123, x1234, y1234] 31 | } 32 | 33 | 34 | export default function (points, data) { 35 | data.value -= data.step 36 | if(data.value <0)data.value=0 37 | var part1OfBezier = sliceBezier( 38 | {x: this.copyPoints[0], y: this.copyPoints[1]}, 39 | {x: this.copyPoints[2], y: this.copyPoints[3]}, 40 | {x: this.copyPoints[4], y: this.copyPoints[5]}, 41 | {x: this.copyPoints[6], y: this.copyPoints[7]},data.value) 42 | 43 | points.forEach((value,index)=>{ 44 | points[index] = part1OfBezier[index] 45 | }) 46 | } -------------------------------------------------------------------------------- /src/motion/index.js: -------------------------------------------------------------------------------- 1 | import dance from './dance.js' 2 | import move from './move.js' 3 | import rotate from './rotate.js' 4 | import path from './path.js' 5 | import to from './to.js' 6 | import line from './line.js' 7 | import circle from './circle.js' 8 | import expand from './expand.js' 9 | import noise from './noise.js' 10 | 11 | export default { 12 | dance, 13 | move, 14 | rotate, 15 | to, 16 | line, 17 | circle, 18 | close: path.close, 19 | open: path.open, 20 | expand, 21 | noise 22 | } -------------------------------------------------------------------------------- /src/motion/line.js: -------------------------------------------------------------------------------- 1 | export default function line(arr) { 2 | 3 | arr[2] += (arr[2] > arr[0] ? -1 : 1); 4 | arr[3] += (arr[3] > arr[1] ? -1 : 1); 5 | arr[4] += (arr[4] > arr[6] ? -1 : 1); 6 | arr[5] += (arr[5] > arr[7] ? -1 : 1); 7 | 8 | 9 | } -------------------------------------------------------------------------------- /src/motion/move.js: -------------------------------------------------------------------------------- 1 | /** 2 | * move motion. 3 | * 4 | * @param {points} 5 | * @param {data} 6 | * data rule example: 7 | * [1, 0.2, -3, 0.7, 0.5, 0.3, -1, 1] 8 | */ 9 | export default function (points, data) { 10 | points.forEach(function (item, index) { 11 | points[index] += data[index] 12 | }) 13 | } -------------------------------------------------------------------------------- /src/motion/noise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * noise motion. 3 | * 4 | * @param {points} 5 | * @param {data} 6 | * data rule example: 7 | * { value : 0, step : 0.005 ,width : 600, height : 400} 8 | */ 9 | import perlin from '../noise.js' 10 | 11 | export default function (points, data) { 12 | data.value += data.step 13 | 14 | points[0] = data.width * perlin.noise(data.value + 15) 15 | points[1] = data.height * perlin.noise(data.value + 25) 16 | points[2] = data.width * perlin.noise(data.value + 35) 17 | points[3] = data.height * perlin.noise(data.value + 45) 18 | points[4] = data.width * perlin.noise(data.value + 55) 19 | points[5] = data.height * perlin.noise(data.value + 65) 20 | points[6] = data.width * perlin.noise(data.value + 75) 21 | points[7] = data.height * perlin.noise(data.value + 85) 22 | 23 | } -------------------------------------------------------------------------------- /src/motion/path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * close or open path motion. 3 | * 4 | * @param {points} 5 | */ 6 | 7 | import vector2 from '../vector2.js' 8 | 9 | function open(points) { 10 | var v = vector2.n(points[0] - points[6], points[1] - points[7]) 11 | 12 | points[0] += v[0] 13 | points[1] += v[1] 14 | 15 | points[6] -= v[0] 16 | points[7] -= v[1] 17 | 18 | } 19 | 20 | function close(points) { 21 | var v = vector2.n(points[0] - points[6], points[1] - points[7]) 22 | 23 | points[0] -= v[0] 24 | points[1] -= v[1] 25 | 26 | points[6] += v[0] 27 | points[7] += v[1] 28 | 29 | } 30 | 31 | function auto(points){ 32 | 33 | 34 | } 35 | 36 | 37 | export default { 38 | close, 39 | open, 40 | auto 41 | } -------------------------------------------------------------------------------- /src/motion/rotate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * rotate motion. 3 | * 4 | * @param {points} 5 | * @param {data} 6 | * data rule example: 7 | * Math.PI/100 8 | */ 9 | 10 | function _rotate(x1, y1, x2, y2, theta, index, points) { 11 | var v = {x: x1 - x2, y: y1 - y2}; 12 | var R = [[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]; 13 | 14 | points[index] = x2 + R[0][0] * v.x + R[0][1] * v.y 15 | points[index + 1] = y2 + R[1][0] * v.x + R[1][1] * v.y 16 | 17 | } 18 | 19 | export default function rotate(points, angle) { 20 | var centerX = (points[0] + points[6]) / 2 21 | var centerY = (points[1] + points[7]) / 2 22 | _rotate(points[0], points[1], centerX, centerY, angle, 0, points) 23 | _rotate(points[2], points[3], centerX, centerY, angle, 2, points) 24 | _rotate(points[4], points[5], centerX, centerY, angle, 4, points) 25 | _rotate(points[6], points[7], centerX, centerY, angle, 6, points) 26 | 27 | } -------------------------------------------------------------------------------- /src/motion/to.js: -------------------------------------------------------------------------------- 1 | /** 2 | * to motion. 3 | * 4 | * @param {points} 5 | * @param {data} 6 | * data rule example: 7 | * [100,200, 150, 333, 200,11, 1, 1] 8 | */ 9 | import vector2 from '../vector2.js' 10 | 11 | export default function to(points, target) { 12 | var va1 = [points[2] - points[0], points[3] - points[1]] 13 | var va2 = [points[4] - points[2], points[5] - points[3]] 14 | var va3 = [points[6] - points[4], points[7] - points[5]] 15 | 16 | var vb1 = [target[2] - target[0], target[3] - target[1]] 17 | var vb2 = [target[4] - target[2], target[5] - target[3]] 18 | var vb3 = [target[6] - target[4], target[7] - target[5]] 19 | 20 | 21 | var v1 = vector2.n((vb1[0] - va1[0]), (vb1[1] - va1[1])) 22 | points[0] -= v1[0] 23 | points[1] -= v1[1] 24 | 25 | var v2 = vector2.n((vb2[0] - va2[0]), (vb2[1] - va2[1])) 26 | points[4] += v2[0] 27 | points[5] += v2[1] 28 | 29 | var v3 = vector2.n((vb3[0] - va3[0]), (vb3[1] - va3[1])) 30 | points[6] += v3[0] 31 | points[7] += v3[1] 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/noise.js: -------------------------------------------------------------------------------- 1 | // from https://github.com/processing/p5.js/blob/master/src/math/noise.js 2 | 3 | ////////////////////////////////////////////////////////////// 4 | 5 | // http://mrl.nyu.edu/~perlin/noise/ 6 | // Adapting from PApplet.java 7 | // which was adapted from toxi 8 | // which was adapted from the german demo group farbrausch 9 | // as used in their demo "art": http://www.farb-rausch.de/fr010src.zip 10 | 11 | // someday we might consider using "improved noise" 12 | // http://mrl.nyu.edu/~perlin/paper445.pdf 13 | // See: https://github.com/shiffman/The-Nature-of-Code-Examples-p5.js/ 14 | // blob/master/introduction/Noise1D/noise.js 15 | 16 | /** 17 | * @module Math 18 | * @submodule Noise 19 | * @for p5 20 | * @requires core 21 | */ 22 | 23 | 'use strict'; 24 | 25 | var p5 = {}; 26 | 27 | var PERLIN_YWRAPB = 4; 28 | var PERLIN_YWRAP = 1<random() function. 47 | * It was invented by Ken Perlin in the 1980s and been used since in 48 | * graphical applications to produce procedural textures, natural motion, 49 | * shapes, terrains etc.

The main difference to the 50 | * random() function is that Perlin noise is defined in an infinite 51 | * n-dimensional space where each pair of coordinates corresponds to a 52 | * fixed semi-random value (fixed only for the lifespan of the program; see 53 | * the noiseSeed() function). p5.js can compute 1D, 2D and 3D noise, 54 | * depending on the number of coordinates given. The resulting value will 55 | * always be between 0.0 and 1.0. The noise value can be animated by moving 56 | * through the noise space as demonstrated in the example above. The 2nd 57 | * and 3rd dimension can also be interpreted as time.

The actual 58 | * noise is structured similar to an audio signal, in respect to the 59 | * function's use of frequencies. Similar to the concept of harmonics in 60 | * physics, perlin noise is computed over several octaves which are added 61 | * together for the final result.

Another way to adjust the 62 | * character of the resulting sequence is the scale of the input 63 | * coordinates. As the function works within an infinite space the value of 64 | * the coordinates doesn't matter as such, only the distance between 65 | * successive coordinates does (eg. when using noise() within a 66 | * loop). As a general rule the smaller the difference between coordinates, 67 | * the smoother the resulting noise sequence will be. Steps of 0.005-0.03 68 | * work best for most applications, but this will differ depending on use. 69 | * 70 | * 71 | * @method noise 72 | * @param {Number} x x-coordinate in noise space 73 | * @param {Number} y y-coordinate in noise space 74 | * @param {Number} z z-coordinate in noise space 75 | * @return {Number} Perlin noise value (between 0 and 1) at specified 76 | * coordinates 77 | * @example 78 | *
79 | * var xoff = 0.0; 80 | * 81 | * function draw() { 82 | * background(204); 83 | * xoff = xoff + .01; 84 | * var n = noise(xoff) * width; 85 | * line(n, 0, n, height); 86 | * } 87 | * 88 | *
89 | *
90 | * var noiseScale=0.02; 91 | * 92 | * function draw() { 93 | * background(0); 94 | * for (var x=0; x < width; x++) { 95 | * var noiseVal = noise((mouseX+x)*noiseScale, mouseY*noiseScale); 96 | * stroke(noiseVal*255); 97 | * line(x, mouseY+noiseVal*80, x, height); 98 | * } 99 | * } 100 | * 101 | *
102 | * 103 | * @alt 104 | * vertical line moves left to right with updating noise values. 105 | * horizontal wave pattern effected by mouse x-position & updating noise values. 106 | * 107 | */ 108 | 109 | p5.noise = function(x,y,z) { 110 | y = y || 0; 111 | z = z || 0; 112 | 113 | if (perlin == null) { 114 | perlin = new Array(PERLIN_SIZE + 1); 115 | for (var i = 0; i < PERLIN_SIZE + 1; i++) { 116 | perlin[i] = Math.random(); 117 | } 118 | } 119 | 120 | if (x<0) { x=-x; } 121 | if (y<0) { y=-y; } 122 | if (z<0) { z=-z; } 123 | 124 | var xi=Math.floor(x), yi=Math.floor(y), zi=Math.floor(z); 125 | var xf = x - xi; 126 | var yf = y - yi; 127 | var zf = z - zi; 128 | var rxf, ryf; 129 | 130 | var r=0; 131 | var ampl=0.5; 132 | 133 | var n1,n2,n3; 134 | 135 | for (var o=0; o=1.0) { xi++; xf--; } 166 | if (yf>=1.0) { yi++; yf--; } 167 | if (zf>=1.0) { zi++; zf--; } 168 | } 169 | return r; 170 | }; 171 | 172 | 173 | /** 174 | * 175 | * Adjusts the character and level of detail produced by the Perlin noise 176 | * function. Similar to harmonics in physics, noise is computed over 177 | * several octaves. Lower octaves contribute more to the output signal and 178 | * as such define the overall intensity of the noise, whereas higher octaves 179 | * create finer grained details in the noise sequence. 180 | *

181 | * By default, noise is computed over 4 octaves with each octave contributing 182 | * exactly half than its predecessor, starting at 50% strength for the 1st 183 | * octave. This falloff amount can be changed by adding an additional function 184 | * parameter. Eg. a falloff factor of 0.75 means each octave will now have 185 | * 75% impact (25% less) of the previous lower octave. Any value between 186 | * 0.0 and 1.0 is valid, however note that values greater than 0.5 might 187 | * result in greater than 1.0 values returned by noise(). 188 | *

189 | * By changing these parameters, the signal created by the noise() 190 | * function can be adapted to fit very specific needs and characteristics. 191 | * 192 | * @method noiseDetail 193 | * @param {Number} lod number of octaves to be used by the noise 194 | * @param {Number} falloff falloff factor for each octave 195 | * @example 196 | *
197 | * 198 | * 199 | * var noiseVal; 200 | * var noiseScale=0.02; 201 | * 202 | * function setup() { 203 | * createCanvas(100,100); 204 | * } 205 | * 206 | * function draw() { 207 | * background(0); 208 | * for (var y = 0; y < height; y++) { 209 | * for (var x = 0; x < width/2; x++) { 210 | * noiseDetail(2,0.2); 211 | * noiseVal = noise((mouseX+x) * noiseScale, 212 | * (mouseY+y) * noiseScale); 213 | * stroke(noiseVal*255); 214 | * point(x,y); 215 | * noiseDetail(8,0.65); 216 | * noiseVal = noise((mouseX + x + width/2) * noiseScale, 217 | * (mouseY + y) * noiseScale); 218 | * stroke(noiseVal*255); 219 | * point(x + width/2, y); 220 | * } 221 | * } 222 | * } 223 | * 224 | *
225 | * 226 | * @alt 227 | * 2 vertical grey smokey patterns affected my mouse x-position and noise. 228 | * 229 | */ 230 | p5.noiseDetail = function(lod, falloff) { 231 | if (lod>0) { perlin_octaves=lod; } 232 | if (falloff>0) { perlin_amp_falloff=falloff; } 233 | }; 234 | 235 | /** 236 | * Sets the seed value for noise(). By default, noise() 237 | * produces different results each time the program is run. Set the 238 | * value parameter to a constant to return the same pseudo-random 239 | * numbers each time the software is run. 240 | * 241 | * @method noiseSeed 242 | * @param {Number} seed the seed value 243 | * @example 244 | *
245 | * var xoff = 0.0; 246 | * 247 | * function setup() { 248 | * noiseSeed(99); 249 | * stroke(0, 10); 250 | * } 251 | * 252 | * function draw() { 253 | * xoff = xoff + .01; 254 | * var n = noise(xoff) * width; 255 | * line(n, 0, n, height); 256 | * } 257 | * 258 | *
259 | * 260 | * @alt 261 | * vertical grey lines drawing in pattern affected by noise. 262 | * 263 | */ 264 | p5.noiseSeed = function(seed) { 265 | // Linear Congruential Generator 266 | // Variant of a Lehman Generator 267 | var lcg = (function() { 268 | // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes 269 | // m is basically chosen to be large (as it is the max period) 270 | // and for its relationships to a and c 271 | var m = 4294967296, 272 | // a - 1 should be divisible by m's prime factors 273 | a = 1664525, 274 | // c and m should be co-prime 275 | c = 1013904223, 276 | seed, z; 277 | return { 278 | setSeed : function(val) { 279 | // pick a random seed if val is undefined or null 280 | // the >>> 0 casts the seed to an unsigned 32-bit integer 281 | z = seed = (val == null ? Math.random() * m : val) >>> 0; 282 | }, 283 | getSeed : function() { 284 | return seed; 285 | }, 286 | rand : function() { 287 | // define the recurrence relationship 288 | z = (a * z + c) % m; 289 | // return a float in [0, 1) 290 | // if z = m then z / m = 0 therefore (z % m) / m < 1 always 291 | return z / m; 292 | } 293 | }; 294 | }()); 295 | 296 | lcg.setSeed(seed); 297 | perlin = new Array(PERLIN_SIZE + 1); 298 | for (var i = 0; i < PERLIN_SIZE + 1; i++) { 299 | perlin[i] = lcg.rand(); 300 | } 301 | }; 302 | 303 | export default p5 304 | -------------------------------------------------------------------------------- /src/q-curve.js: -------------------------------------------------------------------------------- 1 | 2 | class QCurve { 3 | constructor(option) { 4 | this.points = option.points || [0, 0, 0, 0, 0, 0] 5 | this.color = option.color || 'black' 6 | this.x = option.x || 0 7 | this.y = option.y || 0 8 | this.vision = option.vision || [] 9 | this.visionMax = 560 10 | this.visionInterval = option.visionInterval || 10 11 | 12 | this._preDate = Date.now() 13 | this._now = new Date() 14 | 15 | this.data = option.data 16 | 17 | this._noop = function () { 18 | } 19 | this._ease = function (value) { 20 | return value 21 | } 22 | this._targetPoints = null 23 | 24 | this.copyPoints = this.points.slice(0) 25 | this.motion = option.motion || this._noop 26 | 27 | this.visionAlpha = option.visionAlpha === void 0 ? 0.2 : option.visionAlpha 28 | 29 | if (option.initVision === void 0 || option.initVision) { 30 | this._initVision(option.visionCount || 80) 31 | } 32 | } 33 | 34 | _initVision() { 35 | var i = 0; 36 | for (; i < 80; i++) { 37 | this.tick(true) 38 | } 39 | } 40 | 41 | tick(tickSelf) { 42 | this._now = Date.now() 43 | if (this._now - this._preDate > this.visionInterval || tickSelf) { 44 | 45 | this.vision.push.apply(this.vision, this.points) 46 | this.vision.push(this.color) 47 | 48 | if (this.vision.length > this.visionMax) { 49 | this.vision.splice(0, 7) 50 | } 51 | this._preDate = this._now 52 | } 53 | 54 | if (!this.pauseMotion) { 55 | this.motion.call(this, this.points, this.data) 56 | } 57 | 58 | if (this._targetPoints) { 59 | this._pointsTo() 60 | } 61 | } 62 | 63 | pause() { 64 | this.pauseMotion = true 65 | } 66 | 67 | play() { 68 | this.pauseMotion = false 69 | } 70 | 71 | draw(ctx) { 72 | this.tick() 73 | ctx.save() 74 | ctx.translate(this.x, this.y) 75 | ctx.globalAlpha = 1 76 | ctx.strokeStyle = this.color 77 | var points = this.points 78 | ctx.beginPath() 79 | ctx.moveTo.call(ctx, points[0], points[1]) 80 | ctx.quadraticCurveTo.call(ctx, points[2], points[3], points[4], points[5]) 81 | ctx.stroke() 82 | 83 | var vision = this.vision 84 | 85 | var i = 0, len = vision.length 86 | for (; i < len; i += 7) { 87 | ctx.globalAlpha = i / this.visionMax * this.visionAlpha 88 | ctx.strokeStyle = vision[i + 6] 89 | ctx.beginPath() 90 | ctx.moveTo.call(ctx, vision[i], vision[i + 1]) 91 | ctx.bezierCurveTo.call(ctx, vision[i + 2], vision[i + 3], vision[i + 4], vision[i + 5]) 92 | ctx.stroke() 93 | 94 | } 95 | ctx.restore() 96 | } 97 | 98 | 99 | 100 | } 101 | 102 | export default Curve -------------------------------------------------------------------------------- /src/raf.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict'; 3 | 4 | if (!Date.now) 5 | Date.now = function () { return new Date().getTime(); }; 6 | 7 | var vendors = ['webkit', 'moz']; 8 | for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 9 | var vp = vendors[i]; 10 | window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 11 | window.cancelAnimationFrame = (window[vp + 'CancelAnimationFrame'] 12 | || window[vp + 'CancelRequestAnimationFrame']); 13 | } 14 | if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy 15 | || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 16 | var lastTime = 0; 17 | window.requestAnimationFrame = function (callback) { 18 | var now = Date.now(); 19 | var nextTime = Math.max(lastTime + 16, now); 20 | return setTimeout(function () { callback(lastTime = nextTime); }, 21 | nextTime - now); 22 | }; 23 | window.cancelAnimationFrame = clearTimeout; 24 | } 25 | }()); -------------------------------------------------------------------------------- /src/smooth-curve.js: -------------------------------------------------------------------------------- 1 | 2 | class SmoothCurve { 3 | constructor(option) { 4 | this.points = option.points || [0, 0, 0, 0, 0, 0] 5 | this.color = option.color || 'black' 6 | this.x = option.x || 0 7 | this.y = option.y || 0 8 | this.vision = option.vision || [] 9 | this.visionMax = option.visionMax !== void 0 ? option.visionMax : 80 10 | this.visionInterval = option.visionInterval || 10 11 | this.disableVision = option.disableVision 12 | this._preDate = Date.now() 13 | this._now = new Date() 14 | this.debug = option.debug 15 | this.size = option.size || 1 16 | this.data = option.data 17 | 18 | this._stripe = typeof this.color !== 'string' 19 | 20 | const noop = function () { 21 | } 22 | this._ease = function (value) { 23 | return value 24 | } 25 | this._targetPoints = null 26 | 27 | this.copyPoints = this.points.slice(0) 28 | this.motion = option.motion || noop 29 | 30 | this.visionAlpha = option.visionAlpha === void 0 ? 0.2 : option.visionAlpha 31 | 32 | if (option.initVision === void 0 || option.initVision) { 33 | this._initVision(option.visionCount || 80) 34 | } 35 | 36 | this.beforeDraw = option.beforeDraw || noop 37 | this.afterDraw = option.afterDraw || noop 38 | } 39 | 40 | _initVision() { 41 | var i = 0; 42 | for (; i < 80; i++) { 43 | this.tick(true) 44 | } 45 | } 46 | 47 | tick(tickSelf) { 48 | if(!this.disableVision) { 49 | this._now = Date.now() 50 | if (this._now - this._preDate > this.visionInterval || tickSelf) { 51 | 52 | this.vision.push(this.points.slice(0)) 53 | 54 | 55 | if (this.vision.length > this.visionMax) { 56 | this.vision.splice(0, 1) 57 | } 58 | this._preDate = this._now 59 | } 60 | } 61 | 62 | if (!this.pauseMotion) { 63 | this.motion.call(this, this.points, this.data) 64 | } 65 | 66 | if (this._targetPoints) { 67 | this._pointsTo() 68 | } 69 | } 70 | 71 | pause() { 72 | this.pauseMotion = true 73 | } 74 | 75 | play() { 76 | this.pauseMotion = false 77 | } 78 | 79 | draw(ctx) { 80 | this.tick() 81 | 82 | this.beforeDraw.call(this, ctx) 83 | 84 | ctx.save() 85 | ctx.translate(this.x, this.y) 86 | ctx.lineWidth = this.size 87 | ctx.globalAlpha = 1 88 | this._stripe || (ctx.strokeStyle = this.color) 89 | var points = this.points 90 | 91 | if(this._stripe){ 92 | 93 | ctx.beginPath(); 94 | ctx.moveTo(points[0], points[1]); 95 | for (let i = 2, len = points.length; i < len; i += 2) { 96 | 97 | ctx.strokeStyle = this.color[i / 2 % 2] 98 | if (i !== 2) { 99 | ctx.beginPath(); 100 | ctx.moveTo( (points[i-2] + points[i ]) / 2, ((points[i-1] + points[i + 1]) / 2)); 101 | } 102 | if (i === points.length - 4) { 103 | ctx.quadraticCurveTo(points[i], points[i + 1], points[i + 2], points[i + 3]); 104 | } else { 105 | ctx.quadraticCurveTo(points[i], points[i + 1], (points[i] + points[i + 2]) / 2, ((points[i + 1] + points[i + 3]) / 2)); 106 | } 107 | ctx.stroke(); 108 | } 109 | 110 | 111 | 112 | 113 | var vision = this.vision 114 | for (let i = 0, len = vision.length; i < len; i++) { 115 | ctx.globalAlpha = i / this.visionMax * this.visionAlpha 116 | let vp = vision[i] 117 | ctx.beginPath(); 118 | ctx.moveTo(vp[0], vp[1]); 119 | for (let i = 2, vlen = vp.length; i < vlen; i += 2) { 120 | ctx.strokeStyle = this.color[i / 2 % 2] 121 | if (i !== 2) { 122 | ctx.beginPath(); 123 | ctx.moveTo( (vp[i-2] + vp[i ]) / 2, ((vp[i-1] + vp[i + 1]) / 2)); 124 | } 125 | if (i === points.length - 4) { 126 | ctx.quadraticCurveTo(vp[i], vp[i + 1], vp[i + 2], vp[i + 3]); 127 | } else { 128 | ctx.quadraticCurveTo(vp[i], vp[i + 1], (vp[i] + vp[i + 2]) / 2, ((vp[i + 1] + vp[i + 3]) / 2)); 129 | } 130 | 131 | ctx.stroke(); 132 | } 133 | 134 | } 135 | 136 | }else{ 137 | ctx.beginPath(); 138 | ctx.moveTo(points[0], points[1]); 139 | for (let i = 2, len = points.length; i < len; i += 2) { 140 | if (i === points.length - 4) { 141 | ctx.quadraticCurveTo(points[i], points[i + 1], points[i + 2], points[i + 3]); 142 | } else { 143 | ctx.quadraticCurveTo(points[i], points[i + 1], (points[i] + points[i + 2]) / 2, ((points[i + 1] + points[i + 3]) / 2)); 144 | } 145 | } 146 | ctx.stroke(); 147 | 148 | 149 | 150 | var vision = this.vision 151 | for (let i = 0, len = vision.length; i < len; i++) { 152 | ctx.globalAlpha = i / this.visionMax * this.visionAlpha 153 | let vp = vision[i] 154 | ctx.beginPath(); 155 | ctx.moveTo(vp[0], vp[1]); 156 | for (let i = 2, vlen = vp.length; i < vlen; i += 2) { 157 | if (i === points.length - 4) { 158 | ctx.quadraticCurveTo(vp[i], vp[i + 1], vp[i + 2], vp[i + 3]); 159 | } else { 160 | ctx.quadraticCurveTo(vp[i], vp[i + 1], (vp[i] + vp[i + 2]) / 2, ((vp[i + 1] + vp[i + 3]) / 2)); 161 | } 162 | } 163 | ctx.stroke(); 164 | } 165 | 166 | } 167 | 168 | 169 | 170 | if (this.debug) { 171 | ctx.beginPath(); 172 | ctx.globalAlpha = 0.4 173 | ctx.moveTo(points[0], points[1]); 174 | for (let i = 2, len = points.length; i < len; i += 2) { 175 | ctx.lineTo(points[i], points[i + 1]); 176 | } 177 | ctx.stroke(); 178 | } 179 | 180 | ctx.restore() 181 | 182 | this.afterDraw.call(this, ctx) 183 | } 184 | } 185 | 186 | export default SmoothCurve -------------------------------------------------------------------------------- /src/sprout-curve.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by dntzhang on 2017/4/26. 3 | */ 4 | import perlin from './noise.js' 5 | 6 | 7 | class SproutCurve { 8 | constructor(option) { 9 | this.seeds = [] 10 | this.initAlpha = 0.2 11 | this.startLife = 250 12 | this.noise = {value: 0, step: 0.0008, scale: 2, interval: 10} 13 | this.color = option.color || '#65ffff' 14 | } 15 | 16 | addSeed(x, y, vx, vy) { 17 | this.seeds.push({ 18 | x: x, 19 | y: y, 20 | vx: vx, 21 | vy: vy, 22 | px: x, 23 | py: y, 24 | life: this.startLife 25 | }) 26 | 27 | } 28 | 29 | sprout() { 30 | 31 | while (this.seeds.length && this.seeds[0].life === 0) { 32 | this.seeds.shift(); 33 | } 34 | let dx = 0, 35 | dy = 0 36 | for (let i = 0, len = this.seeds.length; i < len; i++) { 37 | dx = dy = 0 38 | let seed = this.seeds[i] 39 | let nv = perlin.noise(this.noise.value + i * this.noise.interval) * Math.PI * 5 40 | this.noise.value += this.noise.step 41 | 42 | dx += this.noise.scale * Math.cos(nv) 43 | dy += this.noise.scale * Math.sin(nv) 44 | seed.life-- 45 | 46 | 47 | let nx = seed.x + dx 48 | let ny = seed.y + dy 49 | 50 | 51 | seed.px = seed.x 52 | seed.py = seed.y 53 | 54 | seed.x = nx 55 | seed.y = ny 56 | //seed.x+=seed.vx*10 57 | //seed.y+=seed.vy*10 58 | 59 | } 60 | } 61 | 62 | 63 | draw(ctx) { 64 | 65 | this.sprout() 66 | if (this.seeds.length === 0)return 67 | 68 | ctx.globalAlpha = this.initAlpha * (this.seeds[this.seeds.length - 1].life / this.startLife) 69 | this.points = [] 70 | 71 | for (let i = 0, len = this.seeds.length; i < len; i++) { 72 | this.points.push(this.seeds[i].x, this.seeds[i].y) 73 | } 74 | let points = this.points 75 | let len = points.length 76 | if (len > 5) { 77 | ctx.beginPath(); 78 | ctx.globalCompositeOperation = 'lighter' 79 | ctx.strokeStyle = this.color 80 | 81 | ctx.moveTo(points[0], points[1]); 82 | for (let i = 2; i < len; i += 2) { 83 | if (i === points.length - 4) { 84 | ctx.quadraticCurveTo(points[i], points[i + 1], points[i + 2], points[i + 3]); 85 | } else { 86 | ctx.quadraticCurveTo(points[i], points[i + 1], (points[i] + points[i + 2]) / 2, ((points[i + 1] + points[i + 3]) / 2)); 87 | } 88 | } 89 | 90 | ctx.stroke() 91 | } 92 | } 93 | } 94 | 95 | export default SproutCurve -------------------------------------------------------------------------------- /src/stage.js: -------------------------------------------------------------------------------- 1 | import Group from './group.js' 2 | 3 | class Stage extends Group { 4 | constructor(width, height, renderTo) { 5 | super() 6 | 7 | if (arguments.length === 1) { 8 | this.canvas = width 9 | this.ctx = this.canvas.getContext('2d') 10 | this.width = this.canvas.width 11 | this.height = this.canvas.height 12 | } else if (arguments.length === 3) { 13 | this.canvas = document.createElement('canvas') 14 | this.canvas.width = width 15 | this.canvas.height = height 16 | this.width = width 17 | this.height = height 18 | this.ctx = this.canvas.getContext('2d') 19 | document.querySelector(renderTo).appendChild(this.canvas) 20 | } 21 | } 22 | 23 | update(notClear) { 24 | if(!notClear){ 25 | this.ctx.clearRect(0, 0, this.width, this.height) 26 | } 27 | this.children.forEach((child)=> { 28 | child.draw(this.ctx) 29 | }) 30 | } 31 | 32 | addExistLine(process) { 33 | } 34 | 35 | sameAs(index) { 36 | } 37 | } 38 | 39 | 40 | export default Stage -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | var util = { 2 | 3 | random: function (min, max) { 4 | return min + Math.floor(Math.random() * (max - min + 1)) 5 | }, 6 | 7 | randomColor: function () { 8 | return ['#22CAB3', '#90CABE', '#A6EFE8', '#C0E9ED', '#C0E9ED', '#DBD4B7', '#D4B879', '#ECCEB2', '#F2ADA6', '#FF7784'][util.random(0, 9)]; 9 | // return '#'+(Math.random()*0xffffff<<0).toString(16); 10 | }, 11 | 12 | map: function (value, start, end, valueStart, valueEnd) { 13 | 14 | return valueStart + (valueEnd - valueStart) * value / (end - start) 15 | }, 16 | 17 | randomSpeed: function () { 18 | return (Math.random() > 0.5 ? 1 : -1) * Math.random() * 2 19 | } 20 | 21 | } 22 | 23 | export default util -------------------------------------------------------------------------------- /src/vector2.js: -------------------------------------------------------------------------------- 1 | function n(x, y) { 2 | var sum = x * x + y * y 3 | if(sum ===0) return [0,0] 4 | var len = Math.sqrt(sum) 5 | return [x / len, y / len] 6 | } 7 | 8 | function sl(x, y) { 9 | return x * x + y * y 10 | } 11 | 12 | 13 | export default { 14 | n, 15 | sl 16 | } -------------------------------------------------------------------------------- /src/word-data.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'c':[ 3 | [70,70,12,76,12,123,70,128] 4 | ], 5 | 'u':[ 6 | [25,68,23,140,80,140,74,100], 7 | [73,62,72,90,75,110,93,134] 8 | ], 9 | 'r':[ 10 | [21,62,21,62,21,128,21,128], 11 | [21,128,17,96,17,68,65,60] 12 | ], 13 | 'v':[ 14 | [12,58,41,132,61,132,96,58] 15 | ], 16 | 'e':[ 17 | [44,104,112,52,-20,82,77,136] 18 | ], 19 | 'j':[ 20 | [52,48,53,48,54,49,52,48], 21 | [8,175,24,204,60,197,56,74] 22 | ], 23 | 's':[ 24 | [81,72,17,42,105,145,41,120] 25 | ], 26 | 'l': [ 27 | [77, 27, 30, 114, 36, 126, 57, 126] 28 | ], 29 | 'i': [ 30 | [56, 71, 56, 72, 59, 70, 56, 73], 31 | [47, 93, 59, 81, 47, 121, 59, 111] 32 | ], 33 | 'p': [ 34 | [57, 59, 46, 100, 42, 139, 41, 160], 35 | [57, 59, 94, 59, 107, 98, 48, 95] 36 | ], 37 | 'g': [ 38 | [81, 59, 78, 91, 71, 194, 29, 147], 39 | [81, 59, 40, 34, 19, 110, 70, 94] 40 | ], 41 | 'b': [ 42 | [41, 27, 33, 95, 33, 133, 34, 134], 43 | [42, 76, 102, 82, 71, 141, 37, 131] 44 | ], 45 | 'h': [ 46 | [33, 28, 46, 64, 41, 137, 23, 125], 47 | [39, 78, 101, 65, 54, 128, 70, 134] 48 | ], 49 | } 50 | -------------------------------------------------------------------------------- /src/word.js: -------------------------------------------------------------------------------- 1 | import wordData from './word-data.js' 2 | import Curve from './curve.js' 3 | import Group from './group.js' 4 | 5 | class Word extends Group { 6 | constructor(word, option) { 7 | super() 8 | 9 | option = Object.assign({ 10 | x:0,y:0,color:'black' 11 | },option) 12 | 13 | this.x = option.x 14 | this.y = option.y 15 | this.color = option.color 16 | this.word = word 17 | this.data = option.data 18 | this.motion = option.motion 19 | 20 | this.points = wordData[word] 21 | 22 | this._init() 23 | } 24 | 25 | _init() { 26 | this.points.forEach((item)=> { 27 | 28 | this.add(new Curve({ 29 | x:this.x, 30 | y:this.y, 31 | points: item, 32 | color:this.color, 33 | data:this.data, 34 | motion:this.motion 35 | })) 36 | }) 37 | } 38 | 39 | } 40 | 41 | export default Word -------------------------------------------------------------------------------- /tutorial/rope.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | [curvejs](https://github.com/AlloyTeam/curvejs)支持[smooth-curve](https://github.com/AlloyTeam/curvejs/blob/master/src/smooth-curve.js)。 4 | 这个smooth-curve非常有用,你只需要给其一堆点,它自动帮你平滑成曲线。 5 | 绳子的本质就是 smooth 的 joint(关节)。 关节是由于各个关节点组成,相互连结且互相约束的物体,常见于各类物理引擎当中。关节的运用非常广泛,例如人体模拟、动物行走模拟、器材、绳子、机关、链桥等都可以灵活利用关节去模拟。 6 | 7 | ![](http://images2015.cnblogs.com/blog/105416/201705/105416-20170505102404836-1010190001.jpg) 8 | 9 | ## 关节 10 | 11 | 关节通常用下面这种表达方式: 12 | 13 | class Joint { 14 | constructor(segLength, segCount, isFixed, startPoint) { 15 | this.segLength = segLength 16 | this.segCount = segCount 17 | this.isFixed = isFixed 18 | this.startPoint = startPoint 19 | this.points = [] 20 | for (var i = 0; i < this.segCount; i++) { 21 | this.points.push(new Vector2(this.startPoint.x, this.startPoint.y + i * this.segLength)) 22 | } 23 | } 24 | } 25 | 26 | 普通的关节分两种,一种是有固定点,一种没有固定点,其中: 27 | 28 | * segLength表示关节每一段的长度(这里假定关节每一段是相等的) 29 | * segCount表示关节个数(包括起点和终点) 30 | * isFixed表示关节是否有固定点(如果isFixed为true,假设startPoint为固定点) 31 | * startPoint表示关节的起点(这里假定关节的初始状态是笔直向下的) 32 | * points表示关节上所有的支点(包括起点和终点) 33 | 34 | 这里需要了解的是 ,在完整的关节表示当中,为了更好的模拟现实世界当中的物体,关节还会加上一个角度区间限制,即关节的最大张开角度和最小的角度。本文的关节不加此限制,任其360度无障碍旋转。 35 | 36 | ## 绘制关节 37 | 38 | 定义一个拥有15个关节段,每段长度为20的关节。完整代码如下所示: 39 | 40 | let canvas = document.getElementById('myCanvas') 41 | let stage = new Stage(canvas) 42 | 43 | let joint = new Joint(20, 15, false, new Vector2(200, 100)), 44 | points = [] 45 | 46 | joint.points.forEach(v=>{ 47 | points.push(v.x,v.y) 48 | }) 49 | 50 | let curve = new SmoothCurve({ 51 | color:["#754726","#42270C"], 52 | points: points, 53 | disableVision:true, 54 | size:4 55 | }) 56 | 57 | stage.add(curve) 58 | 59 | 效果如图所示: 60 | 61 | ## 与鼠标交互 62 | 63 | 要让关节绕着对应的支点动起来,这里让关节的终点跟随鼠标的位置移动。 64 | 65 | 在方法中加入: 66 | 67 | ```js 68 | canvas.addEventListener('mousemove',function(evt){ 69 | var rect = canvas.getBoundingClientRect() 70 | joint.points[joint.points.length - 1] = new Vector2(evt.clientX - rect.left, evt.clientY - rect.top) 71 | joint.updatePointsPosition(joint.points[joint.points.length - 1], joint.points.length - 1) 72 | 73 | 74 | joint.points.forEach((v,i)=>{ 75 | points[i*2] = v.x 76 | points[i*2+1]=v.y 77 | }) 78 | },false) 79 | ``` 80 | 81 | 所以,当鼠标移动的时候,需要实时的更新关节的位置。如下图所示: 82 | 83 | image 84 | 85 | 这里需要注意的两点是: 86 | 87 | 上图描述的是一次微小的拉动,真正要呈现如图所示的前后状态,其实已经经历的很多次位置更新 88 | 89 | 上图反向延长线经过初始点是不准确的,准确的位置是初始点靠右一段距离(取决于两条线段的合力方向,但这不影响关节的模拟) 90 | 91 | image 92 | 93 | 94 | 在分析完具体的过程之后,利用递归的思路依次更新所有的点。更新方法接收两个参数:一个是更新的点、一个是该点的index,当index为1的时候退出递归。 95 | 96 | 97 | var p = Joint.prototype; 98 | p.updatePointsPosition = function (point, index) { 99 | var tempV = this.points[index - 1].sub(point).setLength(this.segLength); 100 | this.points[index - 1] = point.add(tempV); 101 | if (index > 1) { 102 | this.updatePointsPosition(this.points[index - 1], index - 1); 103 | } 104 | } 105 | 106 | 其中var tempV = this.points[index - 1].sub(point).setLength(this.segLength);是计算支点的偏移量,Vector2.setLength是经过normalize(转换为该向量的单位向量) 再multiplyScalar(设置长度)。如下代码所示: 107 | 108 | setLength: function (l) { 109 | 110 | return this.normalize().multiplyScalar(l); 111 | 112 | }, 113 | 完整代码参见Vector2类。 114 | 运行效果如下所示: 115 | 固定起始点 116 | 上面呈现的是没有固定点的关节,那么如果拥有固定点,该怎么更新关节上所有点的位置呢?需要做的仅仅是校正startPoint(启始点、固定点)的位置。 117 | 118 | updatePointsPosition(point, index) { 119 | var tempV = this.points[index - 1].sub(point).setLength(this.segLength) 120 | this.points[index - 1] = point.add(tempV) 121 | if (index > 1) { 122 | this.updatePointsPosition(this.points[index - 1], index - 1) 123 | } else { 124 | if (this.isFixed) { 125 | var v = this.points[0].sub(this.startPoint) 126 | for (var i = 0; i < this.points.length; i++) { 127 | this.points[i].subSelf(v) 128 | } 129 | } 130 | } 131 | } 132 | 133 | 当递归到最后,如果该关节是有固定点的,校正所有关节点的位置。效果如下所示: 134 | -------------------------------------------------------------------------------- /tutorial/smooth-curve.md: -------------------------------------------------------------------------------- 1 | # 两种折线平滑方案 2 | 3 | 平滑折线的场景还是蛮多的,如软体模拟、数学方程可视化、流体模拟、数据可视化、屏保程序[curvejs](https://github.com/AlloyTeam/curvejs)等等方面都有其用武之地。如水的模拟: 4 | 5 | ![usage](http://images0.cnblogs.com/blog2015/105416/201508/251939571561058.png) 6 | 7 | 心形函数方程转图像: 8 | 9 | ![usage](http://images0.cnblogs.com/blog2015/105416/201508/251940011098732.png) 10 | 11 | 因为方程的输入和输出是无限多个,需要绘制方程图像可以只绘制其中一部分点,然后再smooth点连接起的折线。 12 | 13 | 再比如线性报表中的折线smooth化: 14 | 15 | ![usage](http://images0.cnblogs.com/blog2015/105416/201508/251939511257781.png) 16 | 17 | 本文将使用两种方式将折线平滑化,并对比其优劣点: 18 | 19 | * 通过三次贝塞尔曲线将有限个数的点平滑化 20 | * 通过二次贝塞尔曲线将有限个数的点平滑化 21 | 22 | ## 问题建模 23 | 已知若干个点,绘制出该点连接的曲线。 24 | 25 | ```javascript 26 | 27 | 34 | ``` 35 | 36 | 这里实验平台使用浏览器环境,即Canvas相关API以及javascript语言。 37 | 38 | 这里canvas的上下文对象拥有了bezierCurveTo方法: 39 | 40 | ```javascript 41 | context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y); 42 | ``` 43 | 44 | 45 | ## 三次贝塞尔平滑图解 46 | 实现目标 47 | 48 | ![usage](http://images0.cnblogs.com/blog2015/105416/201508/252022052033323.png) 49 | 50 | 具体过程 51 | ![usage](http://images0.cnblogs.com/blog2015/105416/201508/252022149062488.png) 52 | 53 | 54 | ### 代码 55 | Vector2,一般用来表示向量,但有的时候也用来当作点来进行一计算。 56 | ```javascript 57 | var Vector2 = function(x, y) { 58 | this.x = x; 59 | this.y = y; 60 | } 61 | Vector2.prototype = { 62 | "length": function () { 63 | return Math.sqrt(this.x * this.x + this.y * this.y); 64 | }, 65 | "normalize": function () { 66 | var inv = 1 / this.length(); 67 | return new Vector2(this.x * inv, this.y * inv); 68 | }, 69 | "add": function (v) { 70 | return new Vector2(this.x + v.x, this.y + v.y); 71 | }, 72 | "multiply": function (f) { 73 | return new Vector2(this.x * f, this.y * f); 74 | }, 75 | "dot": function (v) { 76 | return this.x * v.x + this.y * v.y; 77 | }, 78 | "angle": function (v) { 79 | return Math.acos(this.dot(v) / (this.length() *v.length())) * 180 / Math.PI; 80 | } 81 | } 82 | ``` 83 | 其中: 84 | 85 | * length求向量长度 86 | * normalize转单位向量 87 | * add向量叠加 88 | * multiply向量翻倍 89 | * dot内积 90 | * angle方法用来求两个向量的夹角 91 | 92 | 核心方法,根据path上的点,求出所有贝塞尔曲线控制点。 93 | 94 | ```javascript 95 | function getControlPoint(path) { 96 | var rt = 0.3; 97 | var i = 0, count = path.length - 2; 98 | var arr = []; 99 | for (; i < count; i++) { 100 | var a = path[i], b = path[i + 1], c = path[i + 2]; 101 | var v1 = new Vector2(a.x - b.x, a.y - b.y); 102 | var v2 = new Vector2(c.x - b.x, c.y - b.y); 103 | var v1Len = v1.length(), v2Len = v2.length(); 104 | var centerV = v1.normalize().add(v2.normalize()).normalize(); 105 | var ncp1 = new Vector2(centerV.y, centerV.x * -1); 106 | var ncp2 = new Vector2(centerV.y * -1, centerV.x); 107 | if (ncp1.angle(v1) < 90) { 108 | var p1 = ncp1.multiply(v1Len * rt).add(b); 109 | var p2 = ncp2.multiply(v2Len * rt).add(b); 110 | arr.push(p1, p2) 111 | } else { 112 | var p1 = ncp1.multiply(v2Len * rt).add(b); 113 | var p2 = ncp2.multiply(v1Len * rt).add(b); 114 | arr.push(p2, p1) 115 | } 116 | } 117 | return arr; 118 | } 119 | ``` 120 | 121 | ### Demo&Source 122 | 123 | * [在线演示](https://alloyteam.github.io/curvejs/asset/smooth.html) 124 | * [源码](https://github.com/AlloyTeam/curvejs/blob/master/asset/smooth.html) 125 | 126 | ## 二次贝塞尔平滑图解 127 | 128 | ![](http://images2015.cnblogs.com/blog/105416/201705/105416-20170508113858863-1718221525.jpg) 129 | 130 | 如上图所示: 131 | 132 | * 除了起点和终点,其余这线上的点全变成二次贝塞尔曲线上的控制点 133 | * 除了起始线段和终点线段,其余线段的**中点**全变成二次贝塞尔曲线的起点和终点 134 | 135 | ### 代码 136 | 137 | 这里canvas的上下文对象拥有了quadraticCurveTo方法: 138 | 139 | ```javascript 140 | context.quadraticCurveTo(cpx,cpy,x,y); 141 | ``` 142 | 143 | 具体实现: 144 | 145 | ``` js 146 | ctx.beginPath(); 147 | ctx.moveTo(points[0], points[1]); 148 | for (let i = 2, len = points.length; i < len; i += 2) { 149 | if (i === points.length - 4) { 150 | ctx.quadraticCurveTo(points[i], points[i + 1], points[i + 2], points[i + 3]); 151 | } else { 152 | ctx.quadraticCurveTo(points[i], points[i + 1], (points[i] + points[i + 2]) / 2, ((points[i + 1] + points[i + 3]) / 2)); 153 | } 154 | } 155 | ctx.stroke(); 156 | ``` 157 | 158 | ### Demo&Source 159 | 160 | * [在线演示](https://alloyteam.github.io/curvejs/asset/smooth2.html) 161 | * [源码](https://github.com/AlloyTeam/curvejs/blob/master/src/smooth-curve.js) 162 | 163 | ## 两种方案对比 164 | 165 | * 三次贝塞尔平滑方案曲线**经过**折线上线段的起点和中点 166 | * 二次贝塞尔平滑方案曲线**不经过**折线上线段的起点和中点 167 | * 三次贝塞尔平滑方案计算量远大于二次贝塞尔平滑方案 168 | * 三次贝塞尔平滑方案在进行交叉或者碰撞检测时显得更加精确 169 | * 二次贝塞尔平滑方案在进行交叉或者碰撞检测时显得不够精确 170 | 171 | 注意,这里的碰撞检测是指的与折线的每条线段是否碰撞来判定是否与曲线碰撞。比如割绳子游戏里的绳子,明显使用三次贝塞尔平滑方案会带来更好的用户体验,当然如果点的数量足够多、点与点的间隔很小的情况下,交叉或者碰撞检测看上去就差别不大。其实上面总结了一大堆可以提炼成一句话: 172 | 173 | * 要想准确Smooth计算量更大(三次贝塞尔平滑方案),否则计算量小(二次贝塞尔平滑方案) 174 | 175 | 关键要看平滑后的作用,如果只是为了视觉效果可以使用二次贝塞尔平滑方案,如果拥有用户交互碰撞检测,可以使用三次贝塞尔平滑方案。 --------------------------------------------------------------------------------