画布请求动画在滑块输入到 JavaScript 变量时超出框架

2024-01-09

我有一个画布元素。它画了一些线,基于艺术消失点 https://en.wikipedia.org/wiki/Vanishing_point.

我正在尝试画一座房子(目前它只是一个盒子)从一个消失点。盒子的大小由delta多变的。如果我手动更改该值,则会执行以下操作:

我想要一个可以改变的滑块delta多变的。但我得到了一些非常奇怪的效果。即,线条被绘制到右侧的框架之外。我到处转储console.log语句,但我仍然找不到问题(如何调试画布问题?)

var canvas = document.querySelector("canvas");

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

canvas.width = 1600;
canvas.height = 800;

var ct = canvas.getContext("2d");

// TODO
// 1. Make a center point
// 2. Draw lines jutting from center
// 3. Draw a line parallel to canvas bottom
// 4. Draw an adjoining item upward

// x, y
// right, down

// Nomenclature
// x0a
// coordinate type, vanishingPt#, endPtName

// Vanishing point 0
var x0 = 400;
var y0 = 400;

// Vanishing point end 0a
var x0a = 0;
var y0a = 2 * y0;

// Vanishing point end 0b
var x0b = 2 * x0;
var y0b = 2 * y0;

// Define delta
var delta = 700;

function init() {
  console.log(delta, "delta");
  console.log(x0b, "x0b");
  console.log(y0b, "y0b");
  console.log(x0, "x0");
  console.log(y0, "y0");
  // First Line
  ct.beginPath();
  ct.moveTo(x0, y0);
  ct.lineTo(x0a, y0a);
  ct.strokeStyle = 'red';
  ct.stroke();

  // Second Line
  ct.beginPath();
  ct.moveTo(x0, y0);
  ct.lineTo(x0b, x0b);
  ct.strokeStyle = 'green';
  ct.stroke();

  // House based on second Line
  ct.beginPath();
  ct.moveTo(x0b, y0b); // starting point
  ct.lineTo(x0b + delta, y0b); // right x+100
  ct.lineTo(x0b + delta, y0b - delta); // up y-100
  ct.lineTo(x0b, y0b - delta); // left x-100
  ct.lineTo(x0b, y0b); // down y+100
  ct.lineTo(x0b, y0b - delta); // back up y-100
  //calculate
  ct.lineTo(x0, y0);
  ct.lineTo(x0b + delta, y0b - delta);
  ct.strokeStyle = 'blue';
  ct.stroke();
}

init();

var slider = document.getElementById("myRange");

slider.oninput = function () {
  delta = this.value;
  requestAnimationFrame(init()); // redraw everything
}
body {
  background-color: lightgray;
  display: flex;
  justify-content: center;
  align-items: center;
}
.slideContainer {
  position: fixed;
  right: 30px;
  top: 30px;
  background-color: lightblue;
  z-index: 20;
}
canvas {
  border: 1px dotted red;
  padding: 80px;
  background-color: lightgray;
  transform: scale(0.5);
}
<!DOCTYPE html>
<html>

<head>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div class="wrapper">
    <div class="slideContainer">
      <input type="range" min="1" max="800" value="50" class="slider" id="myRange">
    </div>
    <canvas id="canvas"></canvas>
  </div>
  <script src="script.js"></script>
</body>

</html>

将渲染与输入事件解耦

您的问题已得到解答,但您的问题有一些不好的做法部分需要指出,否则它们会被复制。

oninput是鼠标移动驱动的事件。

切勿调用渲染函数或requestAnimationFrame来自任何由鼠标移动事件引起的事件。许多设备上的鼠标移动事件的触发速率可能远高于显示速率(高达每秒 1000 次)。显示屏每 60 秒只能显示 1 帧,绘制更多内容将无法看到,并且会消耗客户端的电池。

如果你打电话requestAnimationFrame从鼠标驱动的输入事件中,您最终会排队许多渲染以等待下一次显示刷新,并且requestAnimationFrame尝试平衡负载时,它可能会将渲染排队等待下一帧,因此最新更新最多可能会延迟 2 个显示帧。大多数框架永远不会被看到,而你仍然会消耗能量。

使用信号量和标准渲染循环来监视信号量并仅在需要时重绘,并且每帧仅重绘一次。 (参见示例)

不要缩小画布。

除非您将画布作为动画的一部分进行转换,否则不要通过 CSS 规则将其缩小transform: scale(0.5);(或任何其他缩放方法)渲染性能完全取决于每秒的像素数,如果您将显示画布的大小减半,则意味着您需要渲染 4 倍的像素,并使用 4 倍的内存。

您可以通过 canvas 2D API 进行缩放,这样可以节省客户端的电池寿命并提高性能。

Example

我已经完全重写了代码,希望它会有所帮助。评论了两个要点:更新和规模。添加了使用点而不是 x,y 坐标的代码,因为我很懒。

requestAnimationFrame(update); // start anim loop

const ctx = canvas.getContext("2d");
const width = 1600;  // The ideal resolution
const height = 800;  // used to scale content
canvas.width = innerWidth;
canvas.height = innerHeight;


//Scales 2D context to always show the ideal resolution area
const scaleToFit = () => {  // sets canvas scale to fit content
    var scale = Math.min(canvas.width / width, canvas.height / height);
    ctx.setTransform(scale, 0, 0, scale, 0, 0);
}

var redraw = true;   // when true scene is redrawn ready for the next display refresh

// Working with points is easier
const point = (x = 0, y = 0) => ({x, y});
const pointCpy = (p, x = 0, y = 0) => ({x: p.x + x, y: p.y + y});
const scalePoint = (origin, point, scale) => {
    point.x = (point.x - origin.x) * scale + origin.x;
    point.y = (point.y - origin.y) * scale + origin.y;
};

const p1 = point(400,400);
const pA = point(p1.x, p1.y * 2);
const pB = point(p1.x * 2, p1.y * 2);

var delta = 50;

// the slider input event should not directly trigger a render
slider.addEventListener("input",(e) => {   
    delta = Number(e.target.value); 
    redraw = true;               // use a semaphore to indicate content needs to redraw.
});

function update() {  // this is the render loop it only draws when redraw is true
    if (redraw) {        // monitor semaphore
        redraw = false;  // clear semaphore
        ctx.setTransform(1,0,0,1,0,0);  // resets transform
        ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
        scaleToFit();
        draw();
    }
    requestAnimationFrame(update);
}

// this was your function init()
function draw() {
    drawLine(p1, pA, "red");
    drawLine(p1, pB, "green");
    drawVBox(pB, delta, p1, "blue");
}


function drawVBox(p, size, vp, col, width) { // p is bottom left vp is vanish point
    ctx.strokeStyle = col;
    ctx.lineWidth = width;
    const p0 = pointCpy(p);           // get corners
    const p1 = pointCpy(p, size);      
    const p2 = pointCpy(p, size, -size);
    const p3 = pointCpy(p, 0, -size);
    drawPoly(col, width, p0, p1, p2, p3)

    ctx.beginPath();    // draw vanish lines 
    pathLine(p0, vp);
    pathLine(p1, vp);
    pathLine(p2, vp);
    pathLine(p3, vp);
    ctx.stroke();
    
    const scale = 1 - size / (800 * 2);
    scalePoint(vp, p0, scale);
    scalePoint(vp, p1, scale);
    scalePoint(vp, p2, scale);
    scalePoint(vp, p3, scale);
    drawPoly(col, width, p0, p1, p2, p3);
}   

// Use function to do common tasks and save your self a lot of typing
function drawLine(p1, p2, col, width = 1) { 
    ctx.strokeStyle = col;
    ctx.lineWidth = width;
    ctx.beginPath();
    ctx.lineTo(p1.x, p1.y);  // First point after beginPath can be lineTo
    ctx.lineTo(p2.x, p2.y);
    ctx.stroke();
}
function drawPoly(col,width, ...points) { 
    ctx.strokeStyle = col;
    ctx.lineWidth = width;
    ctx.beginPath();
    for(const p of points){
        ctx.lineTo(p.x, p.y);  // First point after beginPath can be lineTo
    }
    ctx.closePath(); // draw closing line
    ctx.stroke();
}
function pathLine(p1, p2) { 
    ctx.moveTo(p1.x, p1.y);  
    ctx.lineTo(p2.x, p2.y);
}
canvas {

  position : absolute;
  top: 0px;
  left: 0px;
  background-color: lightgray;
  z-index: -20;
}
<canvas id="canvas"></canvas>
<input type="range" min="1" max="800" value="50" id="slider">
<code id="info"></code>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

画布请求动画在滑块输入到 JavaScript 变量时超出框架 的相关文章

随机推荐

  • socket.io 仅将数据包发送给发送者

    我还没有弄清楚如何使用 socket io 直接响应发送者 我了解到 io sockets emit 发送给所有客户端 但我不会将信息发送回发送者 code socket on login function data db users fi
  • Eclipse 中默认显示 CVS 注释

    在 Eclipse 中 您可以通过从上下文菜单中选择 团队 gt 显示注释 来显示文件的 CVS 注释 哪个用户更改了提交中的哪一行 但是 我想默认为 CVS 项目中的所有文件启用此功能 有办法这样做吗 在eclipse中的svn中发现同样
  • C# 将对象序列化为 SOAP 字符串数组问题

    我遇到一个问题 尝试将包含字符串数组的对象序列化为肥皂会导致我的应用程序出现异常 我正在执行以下操作来创建肥皂格式化程序 XmlTypeMapping mapping new SoapReflectionImporter ImportTyp
  • 将嵌套列表分成具有不相交元素的组

    我有一个看起来像这样的列表 my list 1 2 3 4 4 5 6 7 9 10 11 12 我想找到将列表分成两组的最佳方法 以便每组中的各个元素不重叠 例如 在上面的示例中 这两个组将是 group1 1 2 3 4 4 5 6 7
  • PostgreSQL 的 SQLAlchemy func.position() 的语法是什么?

    我正在尝试使用以下命令构建 SQLAlchemy 查询位置 http www postgresql org docs 9 1 static functions string html功能 使用它生成以下形式的 SQL 的正确语法是什么POS
  • 为什么登录本地虚拟机后,共享文件夹中的 git 状态会有所不同?

    我正在一台 OS X 机器上工作 并且有一个运行在上面的 CentOS 7 虚拟机 我在我的驱动器上设置了一个单独的 区分大小写 分区 其中包含在两台计算机之间共享的文件夹 以便我可以在 OS X 中工作 但在 CentOS 中构建 我有理
  • 使用 JQuery 单击特定的提交按钮

    我使用以下命令单击提交按钮 input type submit click 问题是我的页面上有超过 1 个提交按钮 因此我需要定位特定的提交按钮 我怎么能这么做呢 如果你知道数量submit输入以及您想要触发哪一个 按顺序 click然后你
  • Flask-SQLAlchemy 中的 SQLAlchemy 2.0 版本 User.query.get(1)?

    问题 The Query get 方法是在 SQLAlchemy 2 0 中已弃用 https docs sqlalchemy org en 20 orm queryguide query html sqlalchemy orm Query
  • R leaflet - 显示/隐藏带有图层组的 addControl() 元素

    我有一个传单地图 它使用 HTML 的自定义图例并使用addControl函数 如下 R 中自定义标记的传单图例 https stackoverflow com questions 37862467 leaflet legend for c
  • 如何知道Android中的调用活动

    我有一个活动被其他一些活动调用 例如 我有 Activity1 Activity2 和 Activity3 Activity1 调用 Activity2 并传递参数 Activity3 也调用 Activity2 并传递参数 现在 根据调用
  • rJava 安装失败(MacOS、Red Hat)

    我遇到了同样的 rJava 问题 许多其他人似乎都曾在某一时刻遇到过 错误发生在以下情况 R CMD INSTALL测试JNI程序是否可以编译 输出建议运行R CMD javareconf ed or sudo R CMD javareco
  • 一起显示两个视频,然后在单个屏幕上作为合并视频输出

    这个问题听起来可能有点复杂或模棱两可 但我会尽力让它清楚 我已经做了很多谷歌搜索并花了很多时间 但没有找到任何与 Windows 相关的内容 我想在一个屏幕上播放两个视频 一个作为背景的全屏 一个在其顶部的小窗口或右角的小宽度 高度中 然后
  • 如何在 Microsoft Graph Mail API 上使用 $filter 的“in”运算符?

    我正在尝试使用 microsoft graph mail api 使用 in 运算符查询少量电子邮件 但找不到示例 这就是我尝试使用它的方式 https graph microsoft com v1 0 me messages filter
  • 从单个 png 图像中获取图标

    到目前为止我已经见过很多次了 但我自己从未使用过 有人可以解释一下如何从这个单一的 png 图像中获取特定的图标图片 例如我使用 css 用红色选择的图标 它被称作 它用于减少http请求 基本上所有图标都放置在单个画布上并用作backgr
  • Swift Firestore 获取文档 ID

    我正在使用 FirebaseFirestore 作为我的数据库 我有一个带有名称和描述的列表数组 我还想获取每个列表的唯一 documentId 这可能吗 列表 swift struct List var name String var d
  • Angular 2 对预检请求的响应未通过访问控制检查:没有“Access-Control-Allow-Origin”标头

    我是 Angular2 的新手 我正在尝试将 POST 方法调用到我的 net core API 它与 Postman 一起工作正常 但是当我从我的 Angular 2 服务调用它时 它会出现错误 这是我的 api service ts i
  • 从 Xcode Bundle 读取文本文件

    为什么我无法读取 foo rtf 的内容 我已经把它放在 Xcode 包中了 fileRoot 仍然包含 null NSString filePath foo NSString fileRoot NSBundle mainBundle pa
  • 使用 CSS 斑马条纹嵌套列表

    使用交替背景可以轻松设置列表和行的样式 nth child odd even 伪类 但如果您尝试将其应用于嵌套列表 它就会开始看起来很丑陋 我的问题是 是否有任何方法可以按深度 层次结构进行交替 例如 父颜色与子元素的颜色无限期地交替 例如
  • 检查输入的字符串是否仅包含 1 和 0

    我正在编写一个将二进制数转换为十进制数的程序 并且我只是一个新手程序员 我一直试图确定输入的字符串是二进制数 这意味着它只包含 1 和 0 到目前为止我的代码是 String num char n Scanner in new Scanne
  • 画布请求动画在滑块输入到 JavaScript 变量时超出框架

    我有一个画布元素 它画了一些线 基于艺术消失点 https en wikipedia org wiki Vanishing point 我正在尝试画一座房子 目前它只是一个盒子 从一个消失点 盒子的大小由delta多变的 如果我手动更改该值