我想用html5画布命令(例如arc,arcto ......)绘制一个类似于(https://thenounproject.com/term/thumbs-up/70801/)的竖起大拇指图标。我知道这些功能,但我不知道如何开始设计竖起大拇指的图标。
设计应分成3 x 3格的网格。
画布将为300 x 300,画布中的每个单元格将为100 x 100。
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var rectX = 70;
var rectY = 70;
var rectWidth = 50;
var rectHeight = 100;
var cornerRadius = 10;
ctx.lineJoin = "round";
ctx.lineWidth = cornerRadius;
ctx.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
</script>
上面的代码创建了竖起大拇指图标的圆形袖子。
任何人都可以帮助如何创建这样的图标。感谢
答案 0 :(得分:0)
要绘制该形状,您需要绕过路径的角。路径只是一组x和y坐标作为数组。
大多数解决方案都涉及使用贝塞尔曲线,但我发现它们相当难看,因为它们永远不会与圆形相匹配,而在设计世界中,圆形必须是正确的形状(圆形)。
最佳解决方案是使用ctx.arc
函数渲染路径,并为每个角提供起始角度和结束角度。这可能很难在脑海中形象化,因此最好将角定义为具有半径,让机器为您计算起点和终点弧角。
因此,您的路径将具有x,y坐标和每个角的半径。半径0不是圆整的。如果形状是闭合的,您还需要在末端创建圆角,而如果形状是打开的,则不需要。
您的形状数据可能看起来像(不是竖起大拇指)
const shape = [
{x : 10, y : 10, r : 20},
{x : 150, y : 10, r : 50},
{x : 100, y : 100, r : 20},
{x : 50, y : 100, r : 20},
{x : 50, y : 200, r : 20},
{x : 210, y : 100, r : 60},
{x : 210, y : 300, r : 20},
{x : 10, y : 300, r : 20},
"closed",
];
渲染很复杂,每个角都需要找到弧的中心和弧相交的入射线上的点(切线)。我不打算在这里解释数学,因为我认为你不想这样,但如果你对矢量数学感到满意的话,这个例子有一步一步的解释。
通过检查有效解决方案并提供打开和关闭路径的方法(末端有开弧和闭弧),下面的示例可以让您创建所需的形状。 (我不会绘制积分,懒得这样做)
示例绘制两个形状,即闭合和打开。右边的形状也有一个没有半径的角落。它使用ctx.lineJoin = "miter"
来确保角落清晰。虽然您可以通过设置ctx.lineJoin = "round"
来围绕外边缘,并且外部圆角的半径等于线宽的一半。
const ctx = canvas.getContext("2d");
const shape = [
{x : 10, y : 10, r : 20},
{x : 150, y : 10, r : 50},
{x : 100, y : 100, r : 20},
{x : 50, y : 100, r : 20},
{x : 50, y : 200, r : 20},
{x : 210, y : 100, r : 60},
{x : 210, y : 300, r : 20},
{x : 10, y : 300, r : 20},
"closed",
];
const shapeOpen = [
{x : 390, y : 10, r : 0},
{x : 390, y : 300, r : 50},
{x : 310, y : 300, r : 20},
{x : 320, y : 200, r : 0},
{x : 240, y : 200, r : 40},
{x : 240, y : 10, r : 0},
];
// round line ends
ctx.lineCap = "round";
// and sharp corners if no radius
ctx.lineJoin = "miter";
// draw closed and open shapes
drawRoundedShape(shape,12,"Black");
drawRoundedShape(shapeOpen,12,"Black");
function drawRoundedShape(shape, lineWidth, strokeStyle, fillStyle) {
function roundCorner(p1, p2, p3, returnCorner = {}) {
var x, y, v1x, v1y, v2x, v2y, leng, l1, cx, cy;
// Convert lines to normalised vectors from p2
v1x = p1.x - p2.x;
v1y = p1.y - p2.y;
v2x = p3.x - p2.x;
v2y = p3.y - p2.y;
leng = Math.sqrt(v1x * v1x + v1y * v1y);
v1x /= leng;
v1y /= leng;
leng = Math.sqrt(v2x * v2x + v2y * v2y);
v2x /= leng;
v2y /= leng;
// check directions
const cross = (v1x * v2y - v1y * v2x);
// if near parallel then ignore as roundable.
if(Math.abs(cross) < 1e-4){
returnCorner.solutionFound = false;
return returnCorner;
}
// set radius sign, arc direction to match corner
const radius = p2.r * Math.sign(cross);
returnCorner.dir = radius > 0;
returnCorner.solutionFound = true;
// Get distance from p2 to circle tangent along line p2 - p1
l1 = radius / cross;
l1 += l1 * (v1x * v2x + v1y * v2y);
// Find tangent contact on line p2-p1
x = p2.x + v1x * l1;
y = p2.y + v1y * l1;
// Save first tangent point though not needed could come in handy
returnCorner.t1x = x;
returnCorner.t1y = y;
// Find arc center
cx = x - v1y * radius;
cy = y + v1x * radius;
// Find direction of arc start
returnCorner.ang1 = Math.atan2(y - cy, x - cx);
// Find tangent contact on line p2-p3
x = p2.x + v2x * l1;
y = p2.y + v2y * l1;
// Find direction of arc end
returnCorner.ang2 = Math.atan2(y - cy, x - cx);
// Save second tangent point
returnCorner.t2x = x;
returnCorner.t2y = y;
// Save arc center
returnCorner.x = cx;
returnCorner.y = cy;
// Return the corner as an arc
return returnCorner;
}
var corner, i, len, isClosed;
len = shape.length;
isClosed = false;
if (typeof shape[len - 1] === "string" && shape[len - 1].toLowerCase() === "closed") {
len -= 1;
isClosed = true;
}
// shape must have 3 or more points
if (len < 3) { return }
// start rendering
ctx.beginPath();
i = 0;
do {
if (shape[i].r === 0) { // if the radius is 0 then just set the corner point
ctx.lineTo(shape[i].x, shape[i].y);
} else {
// Calculate the corner arc details
corner = roundCorner(
shape[(i + len - 1) % len],
shape[i],
shape[(i + 1) % len],
corner
);
// If a valid solution found render the arc
if (corner.solutionFound) { // only if there is a rounded corner
ctx.arc(
corner.x,
corner.y,
shape[i].r,
corner.ang1,
corner.ang2,
corner.dir
);
} else {
ctx.lineTo(shape[i].x, shape[i].y); // no valid solution so just plot the point
}
}
} while (++i < len);
isClosed && ( ctx.closePath() );
// The path has been created so fill and or stroke it
if (fillStyle) {
ctx.fillStyle = fillStyle;
ctx.fill();
}
if (strokeStyle) {
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeStyle;
ctx.stroke();
}
}
canvas { border : 2px solid black; }
<canvas id="canvas" width = "400" height = "310"></canvas>
备注强>
代码仅在一小组形状上进行了测试,当角落处的弧线位于错误的一侧时,仍可能出现这种情况。如果确实发生了这种情况,那么简单的解决方案就是在角点添加一个标志来反转方向,如果该标志为真,则在roundCorner
函数中否定半径。
同样,由于路径是预先计算的,我没有看到需要限制半径。这意味着如果半径导致切线位于进入角落的线段之外,则路径将在被绘制到切线后自行返回。要修复只是缩小角落的半径以适应。