Анимация запроса холста вне кадра при вводе ползунка в переменную javascript

У меня есть элемент холста. Он рисует несколько линий на основе точки схода искусства.

Я пытаюсь нарисовать дом (пока это просто коробка) из единственной точки схода. Размер коробки определяется переменной delta. Если я изменю значение вручную, произойдет следующее:

Анимация запроса холста вне кадра при вводе ползунка в переменную javascript

Я хотел иметь слайдер, изменяющий переменную delta. Но я получаю действительно странные эффекты. А именно линии выводятся за пределы кадра вправо. Я выкинул повсюду операторы console.info, но все еще не могу найти проблему (как можно даже отладить проблемы с холстом?)

Анимация запроса холста вне кадра при вводе ползунка в переменную javascript

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.info(delta, "delta");
  console.info(x0b, "x0b");
  console.info(y0b, "y0b");
  console.info(x0, "x0");
  console.info(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>
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
234
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

После исправления синтаксической ошибки вызова requestAnimationFrame(init()) вместо requestAnimationFrame(init) обратите внимание на (), все, что осталось, это приведите value вашего HTMLInput к номеру, чтобы вы не выполняли "800" + 150, что приводит к "800150".

myRange.oninput = function() {
  console.info(this.value + 150);
}
<input type = "range" min = "1" max = "800" value = "50" class = "slider" id = "myRange">

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.info(delta, "delta");
  console.info(x0b, "x0b");
  console.info(y0b, "y0b");
  console.info(x0, "x0");
  console.info(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 () {
  // coerce to Number
  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);
}
<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>

ах теперь я понимаю. this.value из слайдера представляет собой строку. Спасибо за помощь :)

Vincent Tang 25.11.2018 06:27
Ответ принят как подходящий

Отделить рендеринг от входных событий

На ваш вопрос был дан ответ, но в вашем вопросе есть некоторые части плохой практики, на которые необходимо указать, или они будут скопированы.

oninput - это событие, управляемое движением мыши.

Никогда не вызывайте функцию рендеринга или requestAnimationFrame из любых событий, которые являются результатом событий перемещения мыши. События перемещения мыши на многих устройствах могут срабатывать со скоростью, намного превышающей скорость отображения (до 1000 раз в секунду). Дисплей может отображать только 1 кадр каждые 60 секунд, рисование больше не будет видно и может проглотить батареи клиента.

Если вы вызываете requestAnimationFrame из события ввода, управляемого мышью, вы в конечном итоге ставите в очередь множество рендеров для следующего обновления дисплея, и, поскольку requestAnimationFrame пытается сбалансировать нагрузку, он может поставить в очередь рендеры для следующего кадра, таким образом, последнее обновление может быть до двух. отображать кадры поздно. Большинство кадров никогда не будут видны, а вы все равно пожираете энергию.

Используйте семафор и стандартный цикл рендеринга, который отслеживает семафор и перерисовывает только при необходимости и только один раз за кадр. (см. пример)

Не уменьшайте масштаб холста.

Если вы не трансформируете холст как часть анимации, не уменьшайте его с помощью правила CSS transform: scale(0.5); (или любого другого метода масштабирования). Производительность рендеринга зависит от пикселей в секунду, если вы вдвое меньше отображаемого холста, это означает, что вам нужно визуализировать в 4 раза больше пикселей и использовать в 4 раза больше памяти.

Вы можете выполнить масштабирование с помощью двумерного API-интерфейса Canvas, что позволит сэкономить заряд батареи клиентов и повысить производительность.

Пример

Я полностью переписал код, надеюсь, это поможет. Прокомментированы два основных момента: Обновления и Масштаб. Добавлен код для использования точек вместо координат 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>

вау, большое спасибо за обзор кода :). Я все еще новичок в использовании <canvas>, так что это было очень полезно

Vincent Tang 26.11.2018 01:05

Это, очевидно, будет зависеть от типа приложения, но для тех, которые рисуют только в ответ на пользовательские события, поддержание активного цикла rAF, вероятно, тоже не лучшее направление ;-) Вместо этого вы можете захотеть дроссель ваших обработчиков событий, поэтому что ваша вкладка может фактически не выполнять вычислений, когда с ней не взаимодействуют. Вот старая и базовая реализация дроссель на основе RF

Kaiido 26.11.2018 16:05

Кадры @Kaiido вызываются как можно скорее после обновления дисплея. Как остановить рендеринг через время после кадра и до следующего обновления дисплея? Даже самый простой рендеринг ctx.clearRect(0,0,1,1) занимает на 2000 раз больше времени, чем if (redraw){ ...}, если redraw - это false. Ваш дроссель в среднем позволяет выполнять 1 дополнительный рендеринг на кадр (округление в меньшую сторону и на медленной машине) при перемещении мыши, поэтому за одну секунду событий перемещения ваш дроссель генерирует ненужные рендеры, потребляющие эквивалент получасовых вызовов незанятых кадров или 1 рендер = 33 секунды не рендеров rAF

Blindman67 26.11.2018 18:19

@ Blindman67 Я думаю, вы забыли сам rAF в своих расчетах ;-) даже если внутренний код, это далеко не нуп. И я не вижу, где вы видите лишний рендеринг, троттлинг в среднем вызывает одну потерю рендеринга (мы всегда на позднем кадре)

Kaiido 26.11.2018 23:30

@ Ваш дроссель допускает дополнительные вызовы rAF в промежутке времени между выходом rAF и следующим обновлением дисплея, которое может длиться до 15 + мс. Seq .. Обновление дисплея, затем событие мыши запрашивает фрейм. Фрейм вызывается сразу после выхода из события, очищает активный флаг и завершается. Затем срабатывает другое событие мыши и запрашивает другой фрейм, который вызывается после выхода из события. это два рендера менее чем за 16 мс. Как остановить рендеринг по истечении времени после кадра и до следующего обновления дисплея?

Blindman67 27.11.2018 00:01

Я думаю, я понимаю, откуда взялось ваше заблуждение. Нет, rAF не выполняет обратный вызов как можно скорее, у rAF есть собственная задача в цикл событий. Сама эта задача вызывается только при необходимости. Живая демонстрация. Теперь наличие непустого список обратных вызовов анимации заставляет браузер думать, что ваша вкладка полностью активна, если она полностью не скрыта, и, таким образом, он будет запускать цикл событий с нормальной частотой плюс вводить задачи рендеринга при 60 Гц, что является не просто задачей if (false) ;-)

Kaiido 27.11.2018 01:40

Если подумать еще раз, если бы он работал так, как вы думали, как бы простой fn => rAF (fn) срабатывал со скоростью 60 кадров в секунду? Если бы он был вызван немедленно в конце цикла событий, то новый rAF будет добавлен перед следующим обновлением экрана, сам вызовется немедленно и добавит новый и т. д. Точно так же, как crazyLoop в моей скрипке, однако это не так, как это работает. Кроме того, cancelAnimationFrame был бы бесполезен, если бы нам пришлось вызывать его из того же цикла событий.

Kaiido 27.11.2018 15:52

@Kaiido Узнал, почему вы не видите того, что раньше происходило. Похоже, что Chrome и FF больше не запускают движения мыши со скоростью выше 1/60 (в соответствии с событиями указателя и касания). Я знаю это только сейчас благодаря эксперименту с Chrome, никогда не позволяющим более одной мыши перемещаться за кадр (между вызовами rAF), а FF очень редко пропускает второе движение мыши (после обновления дисплея и до запроса кадра, таким образом, считается 2, с предыдущим кадром считая 0) Когда это произошло, не знаю. Это объясняет, почему некоторые из моих приложений потеряли точность направления движения мыши.

Blindman67 29.11.2018 15:39

@ Blindman67 нет. Прочтите мой последний комментарий. Что заставит нормальный цикл rAF ждать, но не другие? И в моей скрипке crazyLoop входит в каждый цикл событий (не измерял, но сотни + кадр), и если вы случайно нажмете на «пакет», он будет рекурсивно срабатывать в 1000 раз больше за цикл событий. Итак, мой дроссель работает :-) Это не имеет ничего общего с тем, что UA является порогом частоты событий мыши ... И в любом случае, главное, что наличие непустого списка обратных вызовов анимации помечает ваш документ как анимированный и, следовательно, активный и, следовательно, непостоянный. браузером whoch - это не просто if (false) для аккумуляторов устройства

Kaiido 30.11.2018 00:51

@Kaiido Вызов rAf из контекста выполнения обратного вызова rAF заставляет следующий обратный вызов быть в следующем кадре. Вызов rAF вне контекста выполнения обратного вызова rAF или вызов rAF более одного раза в обратном вызове rAF приведет к вызову 2 или более обратных вызовов для каждого кадра. Он разработан, чтобы позволить нескольким независимым петлям rAF работать вместе со скоростью 60 кадров в секунду. И нет, нет компоновки, если нет изменений в обратных буферах (грязный флаг) (используйте инструменты разработчика, чтобы увидеть). Добавление одного события пользовательского интерфейса на страницу потребляет больше энергии, чем пустой цикл rAF.

Blindman67 30.11.2018 03:16

То, что вы говорите, не имеет смысла ... Какое волшебство было бы в исполнителе обратных вызовов rAF, который сказал бы здесь, что я должен ждать здесь, я не должен. И я не сказал, что они пойдут на самом деле рисовать фрейм (какие инструменты разработки вы говорите, показывают), но они по-прежнему поддерживают весь цикл обработки событий только потому, что документ помечен как анимированный. Пожалуйста, прочтите спецификации и проверьте вкладку «Производительность» в ваших инструментах для разработки. Наличие непрерывного цикла обеспечивает непрерывную работу UA. Ничего не делает. Браузер - это не просто js-исполнитель ...

Kaiido 30.11.2018 03:43

И даже если бы мы предположили, что существует такая магия, как бы вы заставили cancelAnimationFrame работать с вашей логикой? Как вы думаете, они также сохраняют состояние перед выполнением кода в обратном вызове на всякий случай, чтобы они могли волшебным образом восстановить его? Нет, правда в том, что в цикле событий есть специальная задача, которую UA могут посещать только с определенной частотой кадров, и только если они думают, что должны (.7 здесь), и что эта задача сама вызовет под- задача выполнения обратных вызовов rAF. Никакой магии.

Kaiido 30.11.2018 03:46

И на самом деле, хотя они и не должны этого делать, мой Chrome 70 действительно обновляет + композитные слои каждый кадр, когда установлен rAF. jsfiddle.net/L890zep6

Kaiido 30.11.2018 04:06

@Kaiido Не волшебство, это вызванный контекст выполнения, это то, как rAF знает, когда вызывать обратный вызов. cancelAnimationFrame работает так же, как отмена любого таймера, предоставление дескриптора (вы знаете, что у rAF есть возврат, почему это должно быть?). А событие мыши поддерживает события пользовательского интерфейса, что намного сложнее, чем rAF. Что касается сохраненного состояния, я понятия не имею, о чем вы говорите. Попробуйте этот (function A(){requestAnimationFrame(A);requestAnimationFrame(A)})().

Blindman67 30.11.2018 04:09

Позвольте нам продолжить обсуждение в чате.

Kaiido 30.11.2018 04:11

@Kaiido На скрипке !! пожалуйста!?!, код скрипта, который вы запускаете, сильно изменен, страница заполнена событиями и запущены десятки тысяч строк JS. Напишите простой HTML-документ и проверьте его.

Blindman67 30.11.2018 04:15

Тем не менее вы можете видеть, что, когда цикл rAF выключен, в записях perf ничего не сообщается, и что при включении есть js exec + Composite + update.

Kaiido 30.11.2018 04:45

Другие вопросы по теме