效果图:
代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>bezier</title><link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><style>* {margin: 0;padding: 0;}#canvas {background-color: #dcdcdc;margin: 10px;display: block;}.btn {margin-left: 10px;}</style>
</head>
<body><canvas id="canvas" width="800" height="600"></canvas> <button type="button" id="print" class="btn btn-primary">绘制</button><button type="button" id="clear" class="btn btn-primary">清空</button><script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script><script>var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d')
var t = 0 //贝塞尔函数涉及的占比比例,0<=t<=1
var clickNodes = [] //点击的控制点对象数组
var bezierNodes = [] //绘制内部控制点的数组
var isPrinted = false //当前存在绘制的曲线
var isPrinting = false //正在绘制中
var num = 0 //控制点数
var isDrag = false //是否进入拖拽行为
var isDragNode = false //是否点击到了控制点
var dragIndex = 0 //被拖拽的点的索引
var clickon = 0 //鼠标按下时间戳
var clickoff = 0 //鼠标抬起
$(canvas).mousedown(function(e){isDrag = trueclickon = new Date().getTime()var diffLeft = $(this).offset().left,diffTop = $(this).offset().top,clientX = e.clientX,clientY = e.clientY,x = clientX - diffLeft,y = clientY - diffTopclickNodes.forEach(function(item, index) {var absX = Math.abs(item.x - x),absY = Math.abs(item.y - y)if(absX < 5 && absY < 5) {isDragNode = truedragIndex = index}})
}).mousemove(function(e) {if(isDrag && isDragNode) {var diffLeft = $(this).offset().left,diffTop = $(this).offset().top,clientX = e.clientX,clientY = e.clientY,x = clientX - diffLeft,y = clientY - diffTopclickNodes[dragIndex] = {x: x,y: y}ctx.clearRect(0, 0, canvas.width, canvas.height)clickNodes.forEach(function(item, index) {var x = item.x,y = item.y,i = parseInt(index, 10) + 1ctx.fillText("p" + i, x, y + 20)ctx.fillText("p" + i + ': ('+ x +', '+ y +')', 10, i * 20)ctx.beginPath()ctx.arc(x, y, 4, 0, Math.PI * 2, false)ctx.fill()ctx.beginPath()ctx.moveTo(startX, startY)ctx.lineTo(x, y)ctx.strokeStyle = '#696969'ctx.stroke()if (index) {var startX = clickNodes[index - 1].x,startY = clickNodes[index - 1].yctx.beginPath()ctx.moveTo(startX, startY)ctx.lineTo(x, y)ctx.stroke()}})if(isPrinted) {var bezierArr = []for(i = 0; i < 1; i+=0.01) {bezierArr.push(bezier(clickNodes, i))}bezierArr.forEach(function(obj, index) {if (index) {var startX = bezierArr[index - 1].x,startY = bezierArr[index - 1].y,x = obj.x,y = obj.yctx.beginPath()ctx.moveTo(startX, startY)ctx.lineTo(x, y)ctx.strokeStyle = 'red'ctx.stroke()}})}}
}).mouseup(function(e) {isDrag = falseisDragNode = falseclickoff = new Date().getTime()if(clickoff - clickon < 200) {var diffLeft = $(this).offset().left,diffTop = $(this).offset().top,clientX = e.clientX,clientY = e.clientYx = clientX - diffLeft,y = clientY - diffTopif(!isPrinted && !isDragNode) {num++var ctx = canvas.getContext('2d')ctx.font = "16px Microsoft YaHei";ctx.fillStyle = '#696969'ctx.fillText("p" + num, x, y + 20);ctx.fillText("p" + num + ': ('+ x +', '+ y +')', 10, num * 20)ctx.beginPath()ctx.arc(x, y, 4, 0, Math.PI * 2, false)ctx.fill()if(clickNodes.length) {var startX = clickNodes[clickNodes.length - 1].x,startY = clickNodes[clickNodes.length - 1].yctx.beginPath() ctx.moveTo(startX, startY)ctx.lineTo(x, y)ctx.strokeStyle = '#696969'ctx.stroke()} clickNodes.push({x: x,y: y})}}
})
$('#print').click(function() {if(!num) returnif(!isPrinting) {isPrinted = truedrawBezier(ctx, clickNodes)}
})
$('#clear').click(function() {if(!isPrinting) {isPrinted = falsectx.clearRect(0, 0, canvas.width, canvas.height)clickNodes = []bezierNodes = []t = 0num = 0}
})function drawBezier(ctx, origin_nodes) {if(t > 1) {isPrinting = falsereturn}isPrinting = truevar nodes = origin_nodest += 0.01ctx.clearRect(0, 0, canvas.width, canvas.height)drawnode(nodes)window.requestAnimationFrame(drawBezier.bind(this, ctx, nodes))
}
function drawnode(nodes) {if(!nodes.length) returnvar _nodes = nodesvar next_nodes = []_nodes.forEach(function(item, index) {var x = item.x,y = item.y if(_nodes.length === num) {ctx.font = "16px Microsoft YaHei"var i = parseInt(index, 10) + 1ctx.fillText("p" + i, x, y + 20)ctx.fillText("p" + i + ': ('+ x +', '+ y +')', 10, i * 20)}ctx.beginPath()ctx.arc(x, y, 4, 0, Math.PI * 2, false)ctx.fill()if(_nodes.length === 1) {bezierNodes.push(item)if(bezierNodes.length > 1) {bezierNodes.forEach(function (obj, i) {if (i) {var startX = bezierNodes[i - 1].x,startY = bezierNodes[i - 1].y,x = obj.x,y = obj.yctx.beginPath()ctx.moveTo(startX, startY)ctx.lineTo(x, y)ctx.strokeStyle = 'red'ctx.stroke()}})}}if(index) {var startX = _nodes[index - 1].x,startY = _nodes[index - 1].yctx.beginPath()ctx.moveTo(startX, startY)ctx.lineTo(x, y)ctx.strokeStyle = '#696969'ctx.stroke()}}) if(_nodes.length) {for(var i = 0; i < _nodes.length - 1; i++) {var arr = [{x: _nodes[i].x,y: _nodes[i].y}, {x: _nodes[i + 1].x,y: _nodes[i + 1].y }]next_nodes.push(bezier(arr, t))}drawnode(next_nodes)}}
function factorial(num) { //递归阶乘if (num <= 1) {return 1;} else {return num * factorial(num - 1);}
}function bezier(arr, t) { //通过各控制点与占比t计算当前贝塞尔曲线上的点坐标var x = 0,y = 0,n = arr.length - 1arr.forEach(function(item, index) {if(!index) {x += item.x * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) y += item.y * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) } else {x += factorial(n) / factorial(index) / factorial(n - index) * item.x * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) y += factorial(n) / factorial(index) / factorial(n - index) * item.y * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) }})return {x: x,y: y}
}
var getRandomColor = function(){return '#'+Math.floor(Math.random()*16777215).toString(16);
}</script>
</body>
</html>
转自github:
bezierhttps://aaaaaaaty.github.io/bezierMaker.js/playground/playground.html
https://github.com/Aaaaaaaty/bloghttps://github.com/Aaaaaaaty/blog