Commit b9bcf550 authored by JetLu's avatar JetLu Committed by JetLu

🍄

parents
{
"presets": [
["@babel/env", {
"useBuiltIns": "usage",
"modules": "umd"
}]
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-export-default-from",
"@babel/proposal-export-namespace-from",
["@babel/transform-runtime", {
"corejs": 2,
"helpers": false,
"regenerator": true
}]
]
}
\ No newline at end of file
node_modules
package-lock.json
\ No newline at end of file
This diff is collapsed.
{
"name": "moto",
"version": "1.0.0",
"description": "",
"main": "dist/moto.min.js",
"browserslist": [
"ios >= 8",
"android >= 4"
],
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js --host 0.0.0.0",
"build": "webpack -p",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "JetLu",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-export-default-from": "^7.0.0",
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/runtime": "^7.0.0",
"@babel/runtime-corejs2": "^7.1.2",
"babel-loader": "^8.0.2",
"core-js": "^2.5.7",
"css-loader": "^1.0.0",
"html-webpack-plugin": "^3.2.0",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"style-loader": "^0.23.0",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.7"
}
}
## Moto
> 曲线运动库,设计灵感来自`Popmotion`。
## 使用
```bash
# 开发
npm run dev
# 打包
npm run build
# 直接引用
npm i git+ssh://git@gitlab2.dui88.com:lufei/moto.git
```
## 文档
```js
import {curve, tween, easing} from 'moto'
curve.bezier({
p1: {x: 0, y: 0},
p2: {x: 50, y: 50},
p3: {x: 100, y: 0},
duration: 3, // 可选
ease: easing.easeInOut // 时间函数,可选
}).start(v => {
console.log(v)
})
curve.catmullRom(
[{x: 0, y: 0}, {x: 50, y: 50}, {x: 100, y: 0}],
8 // 运动速度,可选
).start({
update: v => console.log(v)
complete: () => {}
})
const anime = tween({
from: {x: 0, scale: 1},
to: {x: 100, scale: 2},
duration: 5, // 可选
ease: easing.linear // 可选
}).start({
update: v => console.log(v),
complete: () => {}
})
setTimeout(() => anime.stop(), 2e3)
```
**`curve.bezier(options)`**
options`{object}`:
- p1: 起始点 `{x, y}`
- p2: 控制点 `{x, y}`
- p3: 结束点 `{x, y}`
- duration: 动画持续时间(单位:`s`),默认 `1`
- ease: 时间函数,默认 `easing.linear`
**`curve.catmullRom(points, [v])`**
- points: 路径点数组 `[{x, y}...]`
- v: 运动速度,默认 `10`
**`tween(options)`**
options`{object}`:
- from: 初始值`{number||object}`
- to: 结束值`{number||object}`
- duration: 动画持续时间(单位:`s`),默认 `1`
- ease: 时间函数,默认 `easing.linear`
以上函数都返回一个`object`:
**`start({function} || {object})`**
- update:
- v: 当前值
- complete:
import {curve, easing, tween} from './core'
HTMLElement.prototype.on = function(...args) {
this.addEventListener(...args)
return this
}
const player = document.querySelector('.player')
document.body.on('pointerdown', ev => {
const target = ev.target
if (target.classList.contains('dot')) {
target.remove()
} else if (target === document.body) {
const
dot = document.createElement('div'),
{pageX: x, pageY: y} = ev
dot.classList.add('dot')
dot.style.top = `${y}px`
dot.style.left = `${x}px`
dot.dataset.position = JSON.stringify({x, y})
document.body.appendChild(dot)
} else if (target.classList.contains('btn-run')) {
target.classList.contains('bezier') && run('bezier')
target.classList.contains('catmull-rom') && run('catmullRom')
target.classList.contains('tween') && run('tween')
} else if (target.classList.contains('btn-clear')) {
clear()
}
})
async function run(action) {
const dots = document.querySelectorAll('.dot')
document.querySelectorAll('.mini-dot').forEach(child => child.remove())
player.setAttribute('style', '')
if (action === 'bezier') {
if (dots.length < 2) return alert('至少 2 个点吧!')
const points = Array.prototype.map.call(dots, dot => JSON.parse(dot.dataset.position))
curve.bezier({
p1: {x: player.offsetLeft, y: player.offsetTop},
p2: points[0],
p3: points[1],
duration: 1,
ease: easing.easeInOut
}).start(trace)
} else if (action === 'catmullRom') {
if (dots.length < 1) return alert('至少 1 个点吧!')
const points = Array.prototype.map.call(dots, dot => JSON.parse(dot.dataset.position))
points.unshift({x: player.offsetLeft, y: player.offsetTop})
curve.catmullRom(points).start(trace)
} else if (action === 'tween') {
if (dots.length < 1) return alert('至少 1 个点吧!')
const points = Array.prototype.map.call(dots, dot => JSON.parse(dot.dataset.position))
tween({
from: {x: player.offsetLeft, y: player.offsetTop, scale: 1},
to: {...points[0], scale: 3},
duration: 1,
ease: easing.easeInOut
}).start(trace)
}
}
function trace(v) {
player.style.left = `${v.x}px`
player.style.top = `${v.y}px`
player.style.transform = `translate(-50%, -50%) scale(${v.scale})`
const dot = document.createElement('i')
dot.classList.add('mini-dot')
dot.style.left = player.style.left
dot.style.top = player.style.top
document.body.appendChild(dot)
}
function clear() {
document.querySelectorAll('.dot').forEach(child => child.remove())
document.querySelectorAll('.mini-dot').forEach(child => child.remove())
player.setAttribute('style', '')
}
function distance(a, b) {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2)
}
import {linear} from '../easing'
export default function(option) {
const {p1, p2, p3, duration = 1, ease = linear} = option
function start(option) {
let id, update, complete, t = 0
if (option instanceof Function) update = option
else ({update, complete} = option)
!function loop() {
t += 1 / 60 / duration
t > 1 ? t = 1 : null
update(bezier(p1, p2, p3, ease(t)))
t === 1 ? complete && complete() : id = requestAnimationFrame(loop)
}()
return {
stop() {
cancelAnimationFrame(id)
}
}
}
return {start}
}
function bezier(p1, p2, p3, t) {
return {
x: calc(p1.x, p2.x, p3.x, t),
y: calc(p1.y, p2.y, p3.y, t)
}
}
function calc(p1, p2, p3, t) {
return (1 - t) ** 2 * p1 + 2 * (1 - t) * t * p2 + t ** 2 * p3
}
\ No newline at end of file
export default function(points, v=10) {
/* catmull rom 不会连接首尾点 */
points.unshift(points[0])
points.push(points[points.length - 1])
function start(option) {
let id, update, complete,
t = 0
if (option instanceof Function) update = option
else ({update, complete} = option)
function* generate() {
for (let i = 0; i + 3 < points.length; i++) {
yield {
p1: points[i],
p2: points[i + 1],
p3: points[i + 2],
p4: points[i + 3],
delta: v / distance(points[i + 1], points[i + 2])
}
}
}
const iterator = generate()
let {p1, p2, p3, p4, delta} = iterator.next().value
!function loop() {
t += delta
t > 1 ? t = 1 : null
update(catmullRom(p1, p2, p3, p4, t))
if (t === 1) {
const {value, done} = iterator.next()
if (done) complete && complete()
else {
({p1, p2, p3, p4, delta} = value)
t = 0
id = requestAnimationFrame(loop)
}
} else id = requestAnimationFrame(loop)
}()
return {
stop() {
cancelAnimationFrame(id)
}
}
}
return {start}
}
function distance(p1, p2) {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2)
}
function catmullRom(p1, p2, p3, p4, t) {
return {
x: calc(p1.x, p2.x, p3.x, p4.x, t),
y: calc(p1.y, p2.y, p3.y, p4.y, t)
}
}
function calc(p1, p2, p3, p4, t) {
const
a = [t ** 3, t ** 2, t, 1],
b = [[-.5, 1.5, -1.5, .5], [1, -2.5, 2, -.5], [-.5, 0, .5, 0], [0, 1, 0, 0]],
c = [p1, p2, p3, p4]
return multiply(
a.map((_, i) => multiply(a, b.map((_, j) => b[j][i]))),
c
)
}
function multiply(a, b) {
return a.map((item, i) => item * b[i])
.reduce((total, current) => total + current)
}
\ No newline at end of file
export bezier from './bezier'
export catmullRom from './catmullRom'
\ No newline at end of file
export function linear(t) {
return t
}
export function easeOut(t) {
return t * (2 - t)
}
export function easeIn(t) {
return t ** 2
}
export function easeInOut(t) {
t *= 2
if (t < 1) return .5 * t ** 2
t--
return .5 * (1 - t * (t - 2))
}
\ No newline at end of file
export * as easing from './easing'
export * as curve from './curve'
export tween from './tween'
import {linear} from './easing'
export default function(option) {
const {from, to, duration = 1, ease = linear} = option
function start(option) {
const complex = isNaN(from)
let update, complete, id, delta, result,
t = 0
complex ? (delta = {}, result = {}, Object.keys(from).forEach(key => {
delta[key] = to[key] - from[key]
})) : delta = to - from
if (option instanceof Function) update = option
else ({update, complete} = option)
!function loop() {
t += 1 / 60 / duration
t > 1 ? t = 1 : null
if (complex) for (const key in delta) {
result[key] = from[key] + delta[key] * ease(t)
} else result = from + delta * ease(t)
update(result)
t === 1 ? complete && complete() : id = requestAnimationFrame(loop)
}()
return {
stop() {
cancelAnimationFrame(id)
}
}
}
return {start}
}
\ No newline at end of file
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
position: relative;
.player {
@size: 4rem;
position: absolute;
width: @size;
height: @size;
border-radius: @size / 2;
background-color: #fc3;
top: 50%;
left: @size;
transform: translate(-50%, -50%);
}
.dot {
@size: 1rem;
width: @size;
height: @size;
border-radius: @size / 2;
background-color: #f3c;
position: absolute;
transform: translate(-50%, -50%);
}
.mini-dot {
@size: .5rem;
width: @size;
height: @size;
border-radius: @size / 2;
background-color: #6cffb5;
position: absolute;
transform: translate(-50%, -50%);
}
}
.btn-group {
display: flex;
button {
// -webkit-appearance: none;
border: none;
color: #fff;
background-color: #007BFF;
font-size: 1rem;
padding: .5rem 1rem;
border-radius: .3rem;
margin-right: 1rem;
cursor: pointer;
}
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>Curve</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdnjs.cloudflare.com/ajax/libs/eruda/1.4.4/eruda.min.js"></script>
<script>//eruda.init()</script>
</head>
<body>
<div class="player"></div>
<section class="btn-group">
<button class="btn-clear">Clear</button>
<button class="btn-run bezier">Bezier</button>
<button class="btn-run catmull-rom">CatmullRom</button>
<button class="btn-run tween">Tween</button>
</section>
</body>
</html>
\ No newline at end of file
const
path = require('path'),
webpack = require('webpack')
module.exports = {
entry: [
'./src/core/index.js'
],
output: {
path: path.resolve('dist'),
filename: 'moto.min.js',
libraryTarget: 'umd',
library: 'moto'
},
stats: 'errors-only',
devtool: false,
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
}
]
},
mode: 'production'
}
\ No newline at end of file
const
path = require('path'),
htmlWebpackPlugin = require('html-webpack-plugin'),
webpack = require('webpack')
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
entry: [
'./src/index.less',
'./src/app.js'
],
output: {
path: path.resolve('dist'),
filename: 'game.js'
},
devServer: {
hot: true,
contentBase: '.',
stats: 'errors-only'
},
stats: 'errors-only',
devtool: isProd ? false : 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
plugins: [
new htmlWebpackPlugin({
template: './src/template.html',
hash: true,
filename: 'index.html',
inject: 'body',
minify: {
collapseWhitespace: true
}
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
mode: isProd ? 'production' : 'development'
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment