画布在向javascript变量输入的滑块上请求动画超出框架 [英] Canvas request animation out of frame on slider input to javascript variable

查看:58
本文介绍了画布在向javascript变量输入的滑块上请求动画超出框架的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个canvas元素。它根据



我想要一个可以更改 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.画出中心点// 2.画出从中心突出的线// 3.画一条与画布底部平行的线// 4.向上// x,y //右,下//命名邻接项// x0a //坐标类型,vanishingPt#,endPtName //消失点0var x0 = 400; var y0 = 400; //消失点终点0avar x0a = 0; var y0a = 2 * y0; //消失点末端0bvar x0b = 2 * x0; var y0b = 2 * y0; //定义deltavar delta = 700; function init(){console.log(delta,三角洲); console.log(x0b, x0b); console.log(y0b, y0b); console.log(x0, x0); console.log(y0, y0); //第一行ct.beginPath(); ct.moveTo(x0,y0); ct.lineTo(x0a,y0a); ct.strokeStyle ='红色'; ct.stroke(); //第二行ct.beginPath(); ct.moveTo(x0,y0); ct.lineTo(x0b,x0b); ct.strokeStyle =绿色; ct.stroke(); //基于第二行ct.beginPath()的房屋; ct.moveTo(x0b,y0b); //起点ct.lineTo(x0b + delta,y0b); //正确的x + 100 ct.lineTo(x0b + delta,y0b-delta); // y-100 ct.lineTo(x0b,y0b-delta); //左x-100 ct.lineTo(x0b,y0b); //向下y + 100 ct.lineTo(x0b,y0b-增量); //备份y-100 //计算ct.lineTo(x0,y0); ct.lineTo(x0b +增量,y0b-增量); ct.strokeStyle ='蓝色'; ct.stroke();} init(); var滑块= document.getElementById( myRange); slider.oninput = function(){delta = this.value; requestAnimationFrame(init()); //重绘所有内容}  

  body {background-color:lightgray ;显示:flex;证明内容:中心; align-items:center;}。slideContainer {position:fixed;右:30px;顶部:30px;背景颜色:浅蓝色; z-index:20;}画布{边框:1px红色点缀;填充:80px;背景色:浅灰色;转换:scale(0.5);}  

 <!DOCTYPE html>< html>< head> < link rel = stylesheet href = style.css />< / head>< body> < div class = wrapper> < div class = slideContainer> <输入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进行缩放,这样可以节省客户端的电池寿命并提高性能


示例


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


  requestAnimationFrame(更新); //开始动画循环

const ctx = canvas.getContext( 2d);
常量宽度= 1600; //理想分辨率
const height = 800; //用于缩放内容
canvas.width = innerWidth;
canvas.height = innerHeight;


//缩放2D上下文以始终显示理想的分辨率区域
const scaleToFit =()=> {//设置画布比例以适合内容
var scale = Math.min(canvas.width / width,canvas.height / height);
ctx.setTransform(scale,0,0,scale,0,0);
}

var redraw = true; //当重绘真实场景以准备下一次显示刷新时

//处理点更容易
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 =(原点,点,比例)=> {
point.x =(point.x-origin.x)*比例+ origin.x;
point.y =(point.y-origin.y)*比例+ origin.y;
};

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

var delta = 50;

//滑块输入事件不应直接触发渲染
slide.addEventListener( input,(e)=> {
delta = Number(e.target .value);
redraw = true; //使用信号灯指示内容需要重绘。

function update(){//这是仅当redraw为true时才绘制的渲染循环
if(redraw){//监视信号量
redraw = false; //清除信号量
ctx.setTransform(1,0,0,1,0,0); //重置转换
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
scaleToFit();
draw();
}
requestAnimationFrame(update);
}

//这是您的函数init()
函数draw(){
drawLine(p1,pA, red);
drawLine(p1,pB,绿色);
drawVBox(pB,delta,p1, blue);
}


函数drawVBox(p,size,vp,col,width){// p是左下角vp是消失点
ctx.strokeStyle = col ;
ctx.lineWidth =宽度;
const p0 = pointCpy(p); //获取角点
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(); //绘制消失线
pathLine(p0,vp);
pathLine(p1,vp);
pathLine(p2,vp);
pathLine(p3,vp);
ctx.stroke();

常量比例= 1-大小/(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);
}

//使用函数执行常见任务并节省大量输入
函数drawLine(p1,p2,col,width = 1){
ctx.strokeStyle = col;
ctx.lineWidth =宽度;
ctx.beginPath();
ctx.lineTo(p1.x,p1.y); // beginPath之后的第一点可以是lineTo
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}
函数drawPoly(col,width,... points){
ctx.strokeStyle = col;
ctx.lineWidth =宽度;
ctx.beginPath();
for(const p of points){
ctx.lineTo(p.x,p.y); // beginPath之后的第一点可以是lineTo
}
ctx.closePath(); //绘制结束线
ctx.stroke();
}
函数pathLine(p1,p2){
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
}

  canvas {

头寸:绝对;
top:0像素;
左:0px;
background-color:浅灰色;
z索引:-20;
}

 < canvas id = canvas > //画布
< input type = range min = 1 max = 800 value = 50 id = slider>
< code id = info>< / code>


I have a canvas element. It draws some lines, based on art vanishing points.

I'm trying to draw a house (for now its just a box) from a single vanishing point. The size of the box is dictated by a delta variable. If I change the value manually, it does this:

I wanted to have a slider that changes the delta variable. But I get some really weird effects. Namely lines are drawn out of frame to the right. I dumped console.log statements everywhere but I still cannot find the problem (how does one even debug canvas issues?)

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>

解决方案

Decouple render from input events

Your question as been answered but your question has some bad practice parts that need to be pointed out or they get copied.

oninput is a mouse move driven event.

Never call a render function or requestAnimationFrame from any events that are the result of mouse move events. Mouse move events on many devices can fire at rates much higher than the display rate (as much as 1000 times a second). The display can only display 1 frame every 60th second, drawing more will not be seen and can chew through the client's batteries.

If you call requestAnimationFrame from within a mouse driven input event you end up queuing many renders for the next display refresh, and as requestAnimationFrame tries to balance the load, it may queue renders for the next frame, thus the latest update can be up to 2 display frames late. Most frames will never be seen and you still chew up power.

Use a semaphore and a standard render loop that monitors the semaphore and redraws only when needed, and only once per frame. (see example)

Don`t scale down the canvas.

Unless you are transforming the canvas as part of an animation dont scale it down via the CSS rule transform: scale(0.5); (or any other scaling method) Rendering performance is all about pixels per second, if you half the size of the displayed canvas that means you need to render 4 times as many pixels, and use 4 times as much memory.

You can do the scaling via the canvas 2D API and will save the clients battery life, and increase performance, doing so.

Example

I have totally re-written the code, hopefully it will help. The two main points, Updates, and Scale are commented. Added code to use points rather than x,y coords as I am lazy.

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>

这篇关于画布在向javascript变量输入的滑块上请求动画超出框架的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆