-
-
Save keelii/d0f278ff571f0addc22f5e2a1ba80abc to your computer and use it in GitHub Desktop.
BezierCurve.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const VIEW_SIZE = 100 | |
function getCursorPosition(canvas, event) { | |
const rect = canvas.getBoundingClientRect() | |
const x = event.pageX - rect.left | |
const y = event.pageY - rect.top | |
return { x, y } | |
} | |
class EasingCanvas extends React.Component { | |
get canvas() { | |
return this.canvasRef.current | |
} | |
get ctx() { | |
return this.canvas.getContext("2d") | |
} | |
get offset() { | |
return { | |
x: (this.props.width - VIEW_SIZE) / 2, | |
y: this.props.height - (this.props.height - VIEW_SIZE) / 2, | |
} | |
} | |
constructor(props) { | |
super(props) | |
this.canvasRef = React.createRef() | |
this.ctrl = null | |
this.prevX = 0 | |
this.prevY = 0 | |
this.value = [...this.props.value] | |
} | |
componentDidMount() { | |
this.draw() | |
} | |
onMouseDown = (e) => { | |
this.mousedown = true | |
this.prevX = e.clientX | |
this.prevY = e.clientY | |
const point = getCursorPosition(this.canvasRef.current, e) | |
const image = this.ctx.getImageData(point.x, point.y, 1, 1) | |
if (image.data[2] === 255) { | |
this.ctrl = "cp1" | |
} | |
if (image.data[2] === 254) { | |
this.ctrl = "cp2" | |
} | |
} | |
onMouseMove = (e) => { | |
if (!this.mousedown) return | |
const deltaX = e.clientX - this.prevX | |
const deltaY = e.clientY - this.prevY | |
this.prevX = e.clientX | |
this.prevY = e.clientY | |
const [cp1x, cp1y, cp2x, cp2y, x, y] = this.value | |
const value = [cp1x, cp1y, cp2x, cp2y, x, y] | |
if (this.ctrl === "cp1") { | |
this.value = [cp1x + deltaX, cp1y + deltaY, cp2x, cp2y, x, y] | |
} | |
if (this.ctrl === "cp2") { | |
this.value = [cp1x, cp1y, cp2x + deltaX, cp2y + deltaY, x, y] | |
} | |
this.forceUpdate() | |
this.draw() | |
} | |
onMouseUp = (e) => { | |
this.mousedown = false | |
this.prevX = 0 | |
this.prevY = 0 | |
this.ctrl = null | |
} | |
draw() { | |
this.clearCanvas() | |
this.drawAxis(this.ctx) | |
this.drawCurve(this.ctx) | |
this.drawControl(this.ctx) | |
} | |
clearCanvas() { | |
this.ctx.clearRect(0, 0, this.props.width, this.props.height) | |
} | |
getBezierCurvePoints() { | |
const offsetX = this.offset.x | |
const offsetY = this.offset.y | |
const x = offsetX + VIEW_SIZE | |
const y = offsetY - VIEW_SIZE | |
return [ | |
offsetX + 35, | |
offsetY + 15, | |
x - 35, | |
y - 15, | |
x, | |
y, | |
] | |
} | |
drawControl(ctx) { | |
const [ | |
cp1x, | |
cp1y, | |
cp2x, | |
cp2y, | |
x, | |
y, | |
] = this.value | |
ctx.save() | |
ctx.beginPath() | |
ctx.setLineDash([2, 4]) | |
ctx.moveTo(this.offset.x, this.offset.y) | |
ctx.lineTo(cp1x, cp1y) | |
ctx.moveTo(x, y) | |
ctx.lineTo(cp2x, cp2y) | |
ctx.stroke() | |
ctx.fillStyle = "rgb(0, 0, 255)" | |
ctx.fillRect(cp1x - 5, cp1y - 5, 10, 10) | |
ctx.fillStyle = "rgb(0, 0, 254)" | |
ctx.fillRect(cp2x - 5, cp2y - 5, 10, 10) | |
ctx.restore() | |
} | |
drawCurve(ctx) { | |
const [ | |
cp1x, | |
cp1y, | |
cp2x, | |
cp2y, | |
x, | |
y, | |
] = this.value | |
ctx.save() | |
ctx.beginPath() | |
ctx.moveTo(this.offset.x, this.offset.y) | |
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) | |
ctx.stroke() | |
ctx.restore() | |
} | |
drawAxis(ctx) { | |
const offsetX = this.offset.x | |
const offsetY = this.offset.y | |
ctx.save() | |
ctx.strokeStyle = "black" | |
ctx.beginPath() | |
ctx.moveTo(offsetX, this.props.height) | |
ctx.lineTo(offsetX, 0) | |
ctx.moveTo(0, offsetY) | |
ctx.lineTo(this.props.width, offsetY) | |
ctx.stroke() | |
const ox = offsetX | |
const oy = offsetY | |
const [ | |
cp1x, | |
cp1y, | |
cp2x, | |
cp2y, | |
x, | |
y, | |
] = this.value | |
ctx.fillStyle = "rgba(0, 0, 0, 0.1)" | |
ctx.fillRect(ox, ox, oy - ox, oy - ox) | |
ctx.fillStyle = "rgba(255, 0, 0, 0.5)" | |
ctx.fillRect(ox - 5, oy - 5, 10, 10) | |
ctx.fillRect(x - 5, y - 5, 10, 10) | |
ctx.restore() | |
} | |
render() { | |
const [cp1x, cp1y, cp2x, cp2y] = this.value | |
return ( | |
<div> | |
[ {(cp1x - this.offset.x) / VIEW_SIZE}, {-(cp1y - this.offset.y) / VIEW_SIZE},{" "} | |
{(cp2x - this.offset.x) / VIEW_SIZE}, {-(cp2y - this.offset.y) / VIEW_SIZE} ]<br /> | |
<canvas | |
onMouseDown={this.onMouseDown} | |
onMouseMove={this.onMouseMove} | |
onMouseUp={this.onMouseUp} | |
ref={this.canvasRef} | |
width={this.props.width} | |
height={this.props.height} | |
style={{ background: "#fff" }} | |
/> | |
</div> | |
) | |
} | |
} | |
function BezierEasingCanvas() { | |
return ( | |
<div style={{ background: "#eee", padding: 100 }}> | |
<div> | |
<strong>贝塞尔缓动函数生成器</strong> | |
<EasingCanvas value={[75, 155, 105, 25, 140, 40]} width={180} height={180} /> | |
</div> | |
</div> | |
) | |
} | |
function App() { | |
return <BezierEasingCanvas /> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment