javascript - 如何使用 for 循环在 html 5 画布中绘制这个指定的图像?
问题描述
我想使用 for 循环在 HTML 5 画布中创建一些模式。我无法创建图片中指定的适当模式。我也在为 for 循环中的端点 x,y 坐标而苦苦挣扎。
<html>
<body>
<style>
*{
margin: 0px;
}
body{
background-color: aqua;
}
canvas
{
background-color: white;
}
</style>
<canvas id="mycanvas" width="5000" height="5000"></canvas>
<script>
var canvas = document.getElementById('mycanvas');
var context = canvas.getContext('2d');
for(i=0; i<1000;i=i+10){
context.moveTo(i, i*50);
context.bezierCurveTo(i*10,i*10,0,100,i,0);
context.stroke();
}
</script>
</body>
</html>
解决方案
这个问题很模糊,因为问题的约束没有定义。该答案分步说明了解决方案。定义函数和数据结构来解决我对问题的解释的子部分。
如果您想要一个即时的剪切和粘贴解决方案,如果不严格定义约束,您将无法获得。
这个答案要求你对 JavaScript 和编程有基本的了解,如果你没有这方面的经验,将无法帮助你解决问题。
重复随机路径。
此解决方案将在画布上重复任意路径。
路径
将路径定义为一组命名函数及其参数。
由于此时我们不知道画布的大小,坐标数据应该很容易缩放以适应任何大小的画布。为此,参数必须是坐标对,并且应该适合大小为 1 x 1 单位的画布(这忽略了画布的方面)。
const path = [
["moveTo", [1.1, -0.1]],
["bezierCurveTo", [0.75, 1.1, 0.5, 0.25, 0.5, 0.5]],
["bezierCurveTo", [0.75, 0.65, 0.25, 0.75, -0.1, 1.1]],
];
请注意,您也可以使用Path2D
对象来定义路径,但这会引入一些关于路径范围的问题。如果我们要覆盖画布,就需要一些东西。
渲染路径
现在是一个渲染路径的函数。
它需要能够
- 知道要利用什么 2D 上下文。
- 将路径移动到相对于画布左上角的位置。
- 缩放路径以适应画布而不改变纵横比
- 以像素为单位设置描边的宽度
- 设置描边样式。
.
function drawFittedPath(ctx, path, x, y, lineWidth, style) {
const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.beginPath();
for(const subPath of path) { ctx[subPathp[0](...subPath[1]) }
ctx.strokeStyle = style;
ctx.lineWidth = lineWidth;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
在哪里渲染
由于需要渲染路径以覆盖整个画布,我们需要知道相对于画布左上角的路径可见的位置。为此,我们可以创建一个查询函数,该函数采用路径和 2D 上下文并返回有关查询的信息。
为了避免重复代码,其中有第二个函数从定义的路径数据中提取 x 或 y 坐标。
function pathQuery(ctx, path, query) {
const pathCoords2Array = (offset, stride = 2) => {
const arr = [];
for(const subPath of path) {
let i = offset;
while (i < subPath[1].length) {
arr.push(subPath[1][i]);
i += stride;
}
}
return arr;
}
query = query.toLowerCase();
const xCoords = pathCoords2Array(0, 2);
const yCoords = pathCoords2Array(1, 2);
const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
const result = {x:0,y:0};
if (query.includes("left")) { result.x = -Math.max(...xCoords) * scale; }
else { result.x = ctx.canvas.width - Math.min(...xCoords) * scale; }
if (query.includes("top")) { result.y = -Math.max(...yCoords) * scale; }
else { result.y = ctx.canvas.height - Math.min(...yCoords) * scale; }
return result;
}
填充画布
现在有了所有功能,我们可以添加渲染路径以填充画布的最后部分。
在这种情况下,我们需要定义路径之间的间距。为简单起见,我们可以将其定义为我们想要查看的路径数,并计算执行此操作所需的 x 和 y 步骤。
function fillCanvasWithPath(ctx, path, count, lineWidth, style) {
const topLeft = pathQuery(ctx, path, "topLeft");
const botRight = pathQuery(ctx, path, "bottomRight");
const xStep = ctx.canvas.width / count;
const yStep = ctx.canvas.height / count;
var x = topLeft.x - lineWidth / 2;
var y = topLeft.y - lineWidth / 2;
while (x < botRight.x + lineWidth && y < botRight.y + lineWidth) {
drawFittedPath(ctx, path, x, y, lineWidth, style);
x += xStep;
y += yStep;
}
}
例子
const path = [
["moveTo", [1.1, -0.1]],
["bezierCurveTo", [0.75, 0.1, 0.5, 0.15, 0.5, 0.3]],
["bezierCurveTo", [0.5, 0.65, 0.05, 0.2, 0.2, 0.6]],
["bezierCurveTo", [0.35, 0.9, 0.0, 0.75, -0.1, 1.1]],
];
const ctx = canvas.getContext("2d");
ctx.lineJoin = "round";
fillCanvasWithPath(ctx, path,50, 6, "#000");
function drawFittedPath(ctx, path, x, y, lineWidth, style) {
const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.beginPath();
for(const subPath of path) { ctx[subPath[0]](...subPath[1]) }
ctx.strokeStyle = style;
ctx.lineWidth = lineWidth;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
function pathQuery(ctx, path, query) {
const pathCoords2Array = (offset, stride = 2) => {
const arr = [];
for(const subPath of path) {
let i = offset;
while (i < subPath[1].length) {
arr.push(subPath[1][i]);
i += stride;
}
}
return arr;
}
query = query.toLowerCase();
const xCoords = pathCoords2Array(0, 2);
const yCoords = pathCoords2Array(1, 2);
const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
const result = {x:0,y:0};
if (query.includes("left")) { result.x = -Math.max(...xCoords) * scale; }
else { result.x = ctx.canvas.width - Math.min(...xCoords) * scale; }
if (query.includes("top")) { result.y = -Math.max(...yCoords) * scale; }
else { result.y = ctx.canvas.height - Math.min(...yCoords) * scale; }
return result;
}
function fillCanvasWithPath(ctx, path, count, lineWidth, style) {
const topLeft = pathQuery(ctx, path, "topLeft");
const botRight = pathQuery(ctx, path, "bottomRight");
const xStep = ctx.canvas.width / count;
const yStep = ctx.canvas.height / count;
var x = topLeft.x - lineWidth / 2;
var y = topLeft.y - lineWidth / 2;
while (x < botRight.x + lineWidth && y < botRight.y + lineWidth) {
drawFittedPath(ctx, path, x, y, lineWidth, style);
x += xStep;
y += yStep;
}
}
canvas {
border: 2px solid #666;
}
<canvas id="canvas" width="1024" height="1024"></canvas>
问题
路径类型
该示例要求查询路径的范围。它假设路径是从坐标对创建的。然而,一些路径可以用比坐标更多的信息来创建,例如ctx.arc
,这些路径的范围并不是那么简单,需要代码来根据可用数据计算实际范围。这超出了基本问题和这个答案的范围。
渲染状态
该解决方案对 2D 上下文状态进行了假设。一些状态会影响每条路径的范围。假设是 2D 上下文处于默认状态。
间距
在您提供的示例图像中,路径在路径上的所有点处等距分布。由于示例解决方案使用任意路径,因此这种限制并不总是可能的,事实上,对于随机路径,这种条件不太可能满足。
解决方案是您必须非常小心您定义的路径。或者您可以忽略约束。
路径
- 需要考虑图像的方面
- 无法关闭
- 必须足够长以覆盖画布
如果您希望每条路径沿其所有点的距离相等,那么您需要使用不同的方法为画布上的每个位置创建自定义路径。这再次超出了简单答案的范围。
推荐阅读
- python - 在python中满足条件后读取下一行的方法
- typescript - 为什么 Typescript 无法找出我的代码中的类型?
- authentication - 命令 ssh-add -K 的“输入身份验证器的 PIN”
- javascript - TypeError:null 不是对象(评估“xmlDocument.getElementsByTagName”)
- vue.js - 如何在我的 Vue 网站中正确设置 Google Analytics?
- php - MySQL是否可以按某一列排序,但如果另一列的值为X,则将其放在最后?
- java - 使用副作用使 java 供应商返回一个值
- pdf - 如何在托管 VM 环境中实施在安全 USB 令牌上交付的 AATL/EUTL 签名证书
- c# - ASP MVC 实体框架数据库上下文和类
- arrays - GNU Make:如何从空格分隔的字符串中设置数组?