Open xinglie opened 5 years ago
给出任意的4个点绘制一个三次贝塞尔曲线,把这个曲线放进外接的一个矩形内
三次贝塞尔曲线网上有很多种实现,我们找到这样的一个方法
let threeBezier = (t, p1, cp1, cp2, p2) => { let { x: x1, y: y1 } = p1; let { x: x2, y: y2 } = p2; let { x: cx1, y: cy1 } = cp1; let { x: cx2, y: cy2 } = cp2; let x = x1 * (1 - t) * (1 - t) * (1 - t) + 3 * cx1 * t * (1 - t) * (1 - t) + 3 * cx2 * t * t * (1 - t) + x2 * t * t * t; let y = y1 * (1 - t) * (1 - t) * (1 - t) + 3 * cy1 * t * (1 - t) * (1 - t) + 3 * cy2 * t * t * (1 - t) + y2 * t * t * t; return { x, y }; }
给出任意的一个进度t(0<=t<=1)和4个点的坐标,我们可以计算出当前贝塞尔曲线上的一个点。
根据上述代码,我们可以推导出一个t的一元三次方程。
根据求根公式,我们只需要计算出在0-1区间内,对应的x和y最大值和最小值即可。
推导出计算区间的函数如下
let getRanges = (p1, cp1, cp2, p2) => { let part = -2 * (3 * p1 - 6 * cp1 + 3 * cp2); let power = Math.pow(3 * p1 - 6 * cp1 + 3 * cp2, 2); let delta = 4 * power - 36 * (p2 - p1 + 3 * cp1 - 3 * cp2) * (cp1 - p1); let down = 6 * (p2 - p1 + 3 * cp1 - 3 * cp2); if (delta > 0) { let sqrt = Math.sqrt(delta); let t1 = (part + sqrt) / down; let t2 = (part - sqrt) / down; if (t1 >= 0 && t2 >= 0 && t1 <= 1 && t2 <= 1) { return [t1, t2, 0, 1]; } else if (t1 >= 0 && t1 <= 1) { return [t1, 0, 1]; } else if (t2 >= 0 && t2 <= 1) { return [t2, 0, 1]; } else { return [0, 1]; } } else if (delta <= 0) { return [0, 1]; } };
这样给出4个点,我们就能算出它x或y的最大值和最小值
然后根据最大x,y和最小x,y画出矩形即可,完成的代码如下
<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <meta http-equiv='X-UA-Compatible' content='IE=edge'> <title>Page Title</title> <meta name='viewport' content='width=device-width, initial-scale=1'> </head> <body> <div style="position:relative;width:500px;height:300px;background:#ccc;margin:50px;"> <svg id="bezier" style="width:100%;height:100%;overflow:visible"> </svg> <div style="position:absolute;border:solid 1px red;pointer-events: none;" id="rect"></div> </div> </body> <script> //p1 cp1 cp2 p2 let bezierPoints = [ { x: 100, y: 50 }, { x: 150, y: 100 }, { x: 260, y: 100 }, { x: 200, y: 200 } ]; let threeBezier = (t, p1, cp1, cp2, p2) => { let { x: x1, y: y1 } = p1; let { x: x2, y: y2 } = p2; let { x: cx1, y: cy1 } = cp1; let { x: cx2, y: cy2 } = cp2; let x = x1 * (1 - t) * (1 - t) * (1 - t) + 3 * cx1 * t * (1 - t) * (1 - t) + 3 * cx2 * t * t * (1 - t) + x2 * t * t * t; let y = y1 * (1 - t) * (1 - t) * (1 - t) + 3 * cy1 * t * (1 - t) * (1 - t) + 3 * cy2 * t * t * (1 - t) + y2 * t * t * t; return { x, y }; } let getRanges = (p1, cp1, cp2, p2) => { let part = -2 * (3 * p1 - 6 * cp1 + 3 * cp2); let power = Math.pow(3 * p1 - 6 * cp1 + 3 * cp2, 2); let delta = 4 * power - 36 * (p2 - p1 + 3 * cp1 - 3 * cp2) * (cp1 - p1); let down = 6 * (p2 - p1 + 3 * cp1 - 3 * cp2); if (delta > 0) { let sqrt = Math.sqrt(delta); let t1 = (part + sqrt) / down; let t2 = (part - sqrt) / down; if (t1 >= 0 && t2 >= 0 && t1 <= 1 && t2 <= 1) { return [t1, t2, 0, 1]; } else if (t1 >= 0 && t1 <= 1) { return [t1, 0, 1]; } else if (t2 >= 0 && t2 <= 1) { return [t2, 0, 1]; } else { return [0, 1]; } } else if (delta <= 0) { return [0, 1]; } }; let updateOutlineRect = () => { let xRanges = getRanges(bezierPoints[0].x, bezierPoints[1].x, bezierPoints[2].x, bezierPoints[3].x); let yRanges = getRanges(bezierPoints[0].y, bezierPoints[1].y, bezierPoints[2].y, bezierPoints[3].y); let xValues = []; for (let xt of xRanges) { xValues.push(threeBezier(xt, bezierPoints[0], bezierPoints[1], bezierPoints[2], bezierPoints[3]).x); } let yValues = []; for (let yt of yRanges) { yValues.push(threeBezier(yt, bezierPoints[0], bezierPoints[1], bezierPoints[2], bezierPoints[3]).y); } let minX = Math.min(...xValues); let maxX = Math.max(...xValues); let minY = Math.min(...yValues); let maxY = Math.max(...yValues); rect.style.left = minX + 'px'; rect.style.top = minY + 'px'; rect.style.width = (maxX - minX) + 'px'; rect.style.height = (maxY - minY) + 'px'; }; let updateBezier = () => { bezier.innerHTML = `<path style="fill:none;stroke:#000;stroke-width:1;" d="M${bezierPoints[0].x},${bezierPoints[0].y} C${bezierPoints[1].x} ${bezierPoints[1].y} ${bezierPoints[2].x} ${bezierPoints[2].y} ${bezierPoints[3].x},${bezierPoints[3].y}"/> <path d="M${bezierPoints[0].x},${bezierPoints[0].y} L${bezierPoints[1].x} ${bezierPoints[1].y} M${bezierPoints[2].x} ${bezierPoints[2].y} L${bezierPoints[3].x},${bezierPoints[3].y}" style="fill:none;stroke:#FA742B;stroke-width:1" /> <circle onmousedown="resize(event,'start')" r="4" cx="${bezierPoints[0].x}" cy="${bezierPoints[0].y}" style="fill: #fff;stroke: #FA742B;" /> <circle onmousedown="ctrl(event,'start')" r="4" cx="${bezierPoints[1].x}" cy="${bezierPoints[1].y}" style="fill: #fff;stroke: #FA742B;" /> <circle onmousedown="ctrl(event)" r="4" cx="${bezierPoints[2].x}" cy="${bezierPoints[2].y}" style="fill: #fff;stroke: #FA742B;" /> <circle onmousedown="resize(event)" r="4" cx="${bezierPoints[3].x}" cy="${bezierPoints[3].y}" style="fill: #fff;stroke: #FA742B;" />`; }; let ctrl = (event, key) => { let index = key == 'start' ? 1 : 2; let startX = bezierPoints[index].x; let startY = bezierPoints[index].y; document.onmousemove = e => { let offsetX = e.pageX - event.pageX; let offsetY = e.pageY - event.pageY; bezierPoints[index].x = startX + offsetX; bezierPoints[index].y = startY + offsetY; updateBezier(); updateOutlineRect(); }; document.onmouseup = e => { document.onmouseup = document.onmousemove = null; }; }; let resize = (event, key) => { let index = key == 'start' ? 0 : 3; let startX = bezierPoints[index].x; let startY = bezierPoints[index].y; document.onmousemove = e => { let offsetX = e.pageX - event.pageX; let offsetY = e.pageY - event.pageY; bezierPoints[index].x = startX + offsetX; bezierPoints[index].y = startY + offsetY; updateBezier(); updateOutlineRect(); }; document.onmouseup = e => { document.onmouseup = document.onmousemove = null; }; }; updateBezier(); updateOutlineRect(); </script> </html>
三次贝塞尔曲线网上有很多种实现,我们找到这样的一个方法
给出任意的一个进度t(0<=t<=1)和4个点的坐标,我们可以计算出当前贝塞尔曲线上的一个点。
根据上述代码,我们可以推导出一个t的一元三次方程。
根据求根公式,我们只需要计算出在0-1区间内,对应的x和y最大值和最小值即可。
推导出计算区间的函数如下
这样给出4个点,我们就能算出它x或y的最大值和最小值
然后根据最大x,y和最小x,y画出矩形即可,完成的代码如下