Поддерживает ли HTML5 / Canvas двойную буферизацию?

83

Я бы хотел нарисовать свою графику в буфере, а затем иметь возможность скопировать ее как есть на холст, чтобы я мог делать анимацию и избегать мерцания. Но я не нашел такой вариант. Кто-нибудь знает, как я могу это сделать?

Shai UI
источник
12
По моему опыту, рисование на холсте объединяется браузером, поэтому анимация получается плавной. Можете ли вы поделиться кодом, который мерцает при описании?
век
2
Я заметил, что IE в некоторых случаях может мерцать при использовании explorercanvas, но это, конечно, не HTML5, а canvasпросто эмулируемый VML элемент. Однако я никогда не видел, чтобы другие браузеры делали это.
крио
3
Связано с stackoverflow.com/questions/11777483
julien
2
Действительно тупой код для новичков, который не мерцает. jsfiddle.net/linuxlizard/ksohjr4f/3 По всем правилам , должно мерцать. Браузеры впечатляют.
Дэвид Пул
Вам нужна только двойная буферизация, если у вас есть функция асинхронного рисования. Пока вы не уступаете браузеру, т.е. делаете рисование синхронным, все будет в порядке. Как только вы добавляете туда обещание или setTimeout или что-то в этом роде, вы возвращаетесь браузеру, и он отрисовывает текущее состояние до его завершения, вызывая мерцание.
albertjan

Ответы:

38

Следующая полезная ссылка, помимо демонстрации примеров и преимуществ использования двойной буферизации, показывает несколько других советов по повышению производительности при использовании элемента холста html5. Он включает ссылки на тесты jsPerf, которые объединяют результаты тестов по браузерам в базу данных Browserscope. Это гарантирует, что советы по производительности проверены.

http://www.html5rocks.com/en/tutorials/canvas/performance/

Для вашего удобства я включил минимальный пример эффективной двойной буферизации, как описано в статье.

// canvas element in DOM
var canvas1 = document.getElementById('canvas1');
var context1 = canvas1.getContext('2d');

// buffer canvas
var canvas2 = document.createElement('canvas');
canvas2.width = 150;
canvas2.height = 150;
var context2 = canvas2.getContext('2d');

// create something on the canvas
context2.beginPath();
context2.moveTo(10,10);
context2.lineTo(10,30);
context2.stroke();

//render the buffered canvas onto the original canvas element
context1.drawImage(canvas2, 0, 0);
Рик Саггс
источник
83

Очень простой метод - иметь два элемента холста в одном месте экрана и установить видимость буфера, который вам нужно показать. Нарисуйте скрытое и переверните, когда закончите.

Некоторый код:

CSS:

canvas { border: 2px solid #000; position:absolute; top:0;left:0; 
visibility: hidden; }

Листаем в JS:

Buffers[1-DrawingBuffer].style.visibility='hidden';
Buffers[DrawingBuffer].style.visibility='visible';

DrawingBuffer=1-DrawingBuffer;

В этом коде массив Buffers [] содержит оба холста-объекта. Поэтому, когда вы хотите начать рисовать, вам все равно нужно получить контекст:

var context = Buffers[DrawingBuffer].getContext('2d');
Федор ван Элдейк
источник
Не по теме: мне лично нравится использовать <noscript>и создавать элементы холста в Javascript. Как правило, вы все равно захотите проверить поддержку холста в Javascript, так зачем вам вставлять резервное сообщение холста в свой HTML?
Shea
19

Все браузеры, которые я тестировал, обрабатывают эту буферизацию за вас, не перерисовывая холст до тех пор, пока не будет завершен код, отрисовывающий ваш фрейм. См. Также список рассылки WHATWG: http://www.mail-archive.com/whatwg@lists.whatwg.org/msg19969.html

Эдвард Коффи
источник
10
Ну замечаю мерцание или разрыв экрана. Не знаю, как это описать. Используется последняя версия Chrome в Linux.
grom
14

Вы всегда можете сделать это var canvas2 = document.createElement("canvas"); и вообще не добавлять его в DOM.

Просто сказать, что раз уж вы, ребята, так одержимы, display:none; мне кажется, что это чище и точнее имитирует идею двойной буферизации, чем просто наличие неловко невидимого холста.

Смертоносный бекон
источник
8

Более двух лет спустя:

Нет необходимости «вручную» реализовывать двойную буферизацию. Об этом г-н Гири написал в своей книге «HTML5 Canvas» .

Для эффективного уменьшения мерцания используйте requestAnimationFrame()!

охагер
источник
1
Как вы объясните улучшение производительности, которое наблюдалось при использовании двойной буферизации? html5rocks.com/en/tutorials/canvas/performance
Рик Саггс
@ricksuggs Ну, "двойная буферизация" или "закадровый рендеринг", упомянутые в html5rocks, немного отличаются от того, о чем я думал. Я рассматривал БД как замену экранных страниц (во vram), что фактически было только операцией указателя, а не копированием фрагментов памяти. OP попросил использовать БД, чтобы избежать мерцания, что действительно решается requestAnimationFrame (). Может быть, эта статья о Offscreen-Rendering может быть интересной. Там я отвечаю на вопрос ОП о копировании и вставке буферизованных данных изображения на экран с использованием Sprite Buffer
ohager
6

Джош спросил (некоторое время назад), как браузер узнает, «когда процесс рисования заканчивается», чтобы избежать мерцания. Я бы прокомментировал его пост, но моя репутация недостаточно высока. Также это только мое мнение. У меня нет фактов, подтверждающих это, но я вполне уверен в этом, и это может быть полезно другим, читающим это в будущем.

Я предполагаю, что браузер не «знает», когда вы закончили рисовать. Но, как и большинство javascript, пока ваш код работает без передачи управления браузеру, браузер по существу заблокирован и не может / не может обновлять / отвечать на свой пользовательский интерфейс. Я предполагаю, что если вы очистите холст и нарисуете весь свой фрейм, не передавая управление браузеру, он не будет рисовать ваш холст, пока вы не закончите.

Если вы настроили ситуацию, когда ваш рендеринг охватывает несколько вызовов setTimeout / setInterval / requestAnimationFrame, когда вы очищаете холст за один вызов и рисуете элементы на своем холсте в следующих нескольких вызовах, повторяя цикл (например) каждые 5 вызовов, я Готов поспорить, что вы увидите мерцание, так как холст будет обновляться после каждого вызова.

Тем не менее, я не уверен, что доверяю этому. Мы уже достигли того момента, когда javascript компилируется в собственный машинный код перед выполнением (по крайней мере, это то, что делает движок Chrome V8, насколько я понимаю). Я не удивлюсь, если не прошло много времени, прежде чем браузеры начали запускать свой javascript в отдельном потоке от пользовательского интерфейса и синхронизировать любой доступ к элементам пользовательского интерфейса, позволяя пользовательскому интерфейсу обновлять / отвечать во время выполнения javascript, который не имел доступа к пользовательскому интерфейсу. Когда / если это произойдет (и я понимаю, что необходимо преодолеть множество препятствий, таких как запуск обработчиков событий, пока вы все еще выполняете другой код), мы, вероятно, увидим мерцание на анимации холста, которая не использует какая-то двойная буферизация.

Лично мне нравится идея двух элементов холста, расположенных друг над другом и чередующихся, которые отображаются / рисуются на каждом кадре. Довольно ненавязчивый и, вероятно, довольно легко добавляемый в существующее приложение с помощью нескольких строк кода.

Ли
источник
Точно. См. Пример JSFiddle здесь, stackoverflow.com/questions/11777483/… .
Эрик
6

Для неверующих вот мерцающий код. Обратите внимание, что я явно очищаю предыдущий круг, чтобы стереть его.

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

function draw_ball(ball) {
    ctx.clearRect(0, 0, 400, 400);
    ctx.fillStyle = "#FF0000";
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, 30, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fill();
}

var deltat = 1;
var ball = {};
ball.y = 0;
ball.x = 200;
ball.vy = 0;
ball.vx = 10;
ball.ay = 9.8;
ball.ax = 0.1;

function compute_position() {
    if (ball.y > 370 && ball.vy > 0) {
        ball.vy = -ball.vy * 84 / 86;
    }
    if (ball.x < 30) {
        ball.vx = -ball.vx;
        ball.ax = -ball.ax;
    } else if (ball.x > 370) {
        ball.vx = -ball.vx;
        ball.ax = -ball.ax;
    }
    ball.ax = ball.ax / 2;
    ball.vx = ball.vx * 185 / 186;
    ball.y = ball.y + ball.vy * deltat + ball.ay * deltat * deltat / 2
    ball.x = ball.x + ball.vx * deltat + ball.ax * deltat * deltat / 2
    ball.vy = ball.vy + ball.ay * deltat
    ball.vx = ball.vx + ball.ax * deltat
    draw_ball(ball);
}

setInterval(compute_position, 40);
<!DOCTYPE html>
<html>
<head><title>Basketball</title></head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
</body></html>

Джон
источник
4
Мне кажется, что он не мерцает, по крайней мере, когда мяч не движется больше, чем его радиус каждый кадр. Chrome / Windows.
Жюль
10
Я добавил вызов requestAnimFrame - см. Здесь - к вашему примеру на jsfiddle.net/GzSWJ/28 - он больше не
мерцает
5

В браузерах нет мерцания! Они уже используют буферизацию dbl для рендеринга. Js-движок выполнит весь ваш рендеринг перед его показом. Кроме того, контекстное сохранение и восстановление только данных матрицы преобразования стека и т.п., но не самого содержимого холста. Итак, вам не нужна и не нужна буферизация dbl!

Лука
источник
8
Можете ли вы предоставить доказательства, подтверждающие ваши утверждения?
Рик Саггс
@ricksuggs Я обнаружил, что неиспользование БД приводит к резким рывкам в анимации, я еще не пробовал БД
FutuToad
3

Вместо того, чтобы использовать свою собственную, вы, вероятно, извлечете максимальную пользу, используя существующую библиотеку для создания чистой и немерцающей анимации JavaScript:

Вот популярный: http://processingjs.org

a7drew
источник
95
Точно! Зачем писать 10 строк собственного кода, если можно просто использовать целую библиотеку 275 КБ, не имея ни малейшего представления об этом !? Да, я был саркастичен.
Том
3

вам нужно 2 холста: (обратите внимание на z-index css и положение: absolute)

<canvas id="layer1" width="760" height="600" style=" position:absolute; top:0;left:0; 
visibility: visible;  z-index: 0; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<canvas id="layer2" width="760" height="600" style="position:absolute; top:0;left:0; 
visibility: visible;  z-index: 1; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>

вы можете заметить, что первый холст виден, а второй - скрыт. Идея рисовать на скрытом, после чего мы скроем видимое и сделаем скрытый холст видимым. когда он скрыт 'очистить скрытый холст

<script type="text/javascript">
var buff=new Array(2);
buff[0]=document.getElementById("layer1");
buff[1]=document.getElementById("layer2");

ctx[0]=buff[0].getContext("2d");
ctx[1]=buff[1].getContext("2d");
var current=0;
// draw the canvas (ctx[ current ]);
buff[1- current ].style.visibility='hidden';
buff[ current ].style.visibility='visible';
ctx[1-current].clearRect(0,0,760,600);
current =1-current;
Авиад Гиспан
источник
Хорошая реализация, если кому-то нужно вручную применить технику двойной буферизации. Просто добавьте следующую строку: var ctx = new Array (2); в пустую строку в вашем коде выше.
dimmat 02
2

Opera 9.10 работает очень медленно и показывает процесс рисования. Если вы хотите, чтобы браузер не использовал двойную буферизацию, попробуйте Opera 9.10.

Некоторые люди предположили, что браузеры каким-то образом определяют, когда процесс рисования заканчивается, но можете ли вы объяснить, как это может работать? Я не заметил явного мерцания в Firefox, Chrome или IE9, даже когда отрисовка идет медленно, поэтому кажется, что это именно то, что они делают, но как это делается, для меня загадка. Как браузер может когда-либо узнать, что он обновляет отображение непосредственно перед выполнением других инструкций по рисованию? Как вы думаете, они просто рассчитывают время, чтобы, если интервал более 5 мс или около того проходит без выполнения инструкции по рисованию холста, предполагается, что он может безопасно менять буферы?

Джош
источник
2

В большинстве случаев этого делать не нужно, браузер реализует это за вас. Но не всегда полезно!

Вам все равно придется реализовать это, когда ваш рисунок очень сложен. Большая часть частоты обновления экрана составляет около 60 Гц, это означает, что экран обновляется каждые 16 мс. Частота обновления браузера может приближаться к этому числу. Если для завершения вашей фигуры требуется 100 мс, вы увидите незавершенную фигуру. Таким образом, вы можете реализовать двойную буферизацию в этой ситуации.

Я провел тест: Clear a rect, wait for some time, then fill with some color.если я установлю время на 10 мс, я не увижу мерцания. Но если установить 20 мс, мерцание будет.

Цзянь-Вэй Хуанг
источник