Статьи

Освоение холста HTML5: часть 1

Это сообщение от Грэма Мюррея из блога Infragistics.

Это первая из серии публикаций в блоге о освоении элемента HTML5 canvas. Сначала мы сконцентрируемся на анимации, поскольку это один из лучших способов использовать холст, чтобы привнести новые типы взаимодействия и визуализации в ваши веб-приложения. Вот предварительный просмотр того, что мы строим для первой части.

Обзор холста

Элемент HTML5 canvas предоставляет API для рендеринга 2-D в непосредственном режиме, который мы можем использовать в наших веб-приложениях. Он не зависит от плагинов браузера и требует только достаточно современного браузера, который поддерживает компонент холста HTML5. Даже если вы пытаетесь поддерживать более старые версии Internet Explorer (8 и ниже), есть некоторые полифилы, которые можно использовать для обеспечения их поддержки (FlashCanvas, exCanvas и т. Д.).  

Поскольку canvas — это механизм рендеринга в непосредственном режиме, а не для построения графа объектов, представляющего все видимые объекты (как это было бы в системе с сохраненным режимом, такой как Silverlight или WPF), мы поговорим о контексте рендеринга canvas и для каждого кадра мы сообщим ему, какие визуальные примитивы будут отображаться в каких местах.

Привет круг

Для начала давайте нарисуем круг на экране.

Мы будем предполагать, что jQuery был указан для этого веб-приложения, чтобы помочь нам в сжатой манипуляции с необходимыми элементами DOM. Но jQuery, безусловно, не требуется для взаимодействия с холстом. Сначала мы начнем с DOM. Нам нужно добавить элемент холста где-нибудь в нашей иерархии DOM, где мы хотим отобразить контент холста. В данном случае это единственный элемент в теле моей страницы.

<canvas id="canvas"></canvas> 

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

Теперь пришло время сделать круг на холсте. Нам нужно начать с получения 2-D контекста рендеринга с холста. Контекст рендеринга — это то, что отображает нам API, который нам необходим для рендеринга примитивов на холст.

    $(function () {  
        var canv, context;  
          
        $("#canvas").attr("width", "500px").attr("height", "500px");  
        canv = $("#canvas")[0];  
        context = canv.getContext("2d");  

Здесь мы используем jQuery, чтобы найти холст по id, а затем установить его ширину и высоту. Что-то, что может сначала сбить вас с толку, это то, что на самом деле есть две ширины и высоты, которые имеют отношение к холсту. Элемент canvas имеет атрибуты width и height, которые представляют размер растрового изображения, используемого для визуализации содержимого в пределах canvas. Между тем, вы также можете установить ширину и высоту CSS на холсте. Они представляют размер, до которого растровое изображение, генерируемое холстом, масштабируется при отображении на странице. Так, например, если вы создали холст 50 на 50 пикселей и масштабировали его до 500 на 500 пикселей, это выглядело бы очень блочно, поскольку вы растягивали бы квадрат на 50 пикселей, чтобы заполнить площадь на 500 пикселей. В большинстве случаев вы, вероятно, захотите, чтобы размер CSS совпадал с размером атрибута холста.

Затем мы извлекаем ссылку на элемент DOM canvas из jQuery и вызываем getContext («2d»), чтобы получить контекст рендеринга. Почему getContext («2d»)? Это потому, что холст HTML5 также играет неотъемлемую роль в WebGL, помимо его 2-D API, но сейчас мы говорим о 2-D.

Затем мы визуализируем фактический круг. Здесь я определяю функцию «draw», которая будет визуализировать круг, а затем вызывать его для фактического рендеринга. Почему я разделил это? Это станет очевидным, когда мы добавим анимацию в микс.

    function draw() {  
            context.fillStyle = "rgb(255,0,0)";  
            context.beginPath();  
            context.arc(150, 150, 120, 0, 2 * Math.PI, false);  
            context.fill();  
        }  
      
        draw();  

Мы здесь:

  • Установка стиля заливки холста на красный; Вы можете использовать большинство синтаксиса, которые будет принимать свойство цвета CSS.
  • Говоря контексту, чтобы начать новый путь
  • Рисование дуги с центром в 150px и 150px с радиусом 120, с поворотом от нуля до 360 градусов, по часовой стрелке (обратите внимание, что API на самом деле хочет радианы, а не градусы.)
  • Говоря контексту, чтобы заполнить путь, который мы определили, таким образом, заполняя круг

И вот результат .

Привет Анимация

Теперь давайте оживим наш круг .

Во-первых, вот измененная логика из нашей предыдущей версии:

    var canv, context, lastTime, duration, progress, forward = true;  
          
    $("#canvas").attr("width", "500px").attr("height", "500px");  
    canv = $("#canvas")[0];  
    context = canv.getContext("2d");  
      
    lastTime = new Date().getTime();  
      
    duration = 1000;  
    progress = 0;  
      
    function draw() {  
        var elapsed, time, b;  
          
        time = new Date().getTime();  
        elapsed = time - lastTime;  
        lastTime = time;  
      
        if (forward) {  
            progress += elapsed / duration;  
        } else {  
            progress -= elapsed / duration;  
        }  
        if (progress > 1.0) {  
            progress = 1.0;  
            forward = false;  
        }  
        if (progress < 0) {  
            progress = 0;  
            forward = true;  
        }  
      
        b = 255.0 * progress;  
        context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";  
        context.beginPath();  
        context.arc(150, 150, 120, 0, 2 * Math.PI, false);  
        context.fill();  
    }  
      
    window.setInterval(draw, 1000 / 60.0);  

Итак, что отличия выше?

Во-первых, мы используем window.setInterval для повторного вызова нашего метода draw. Мы пытаемся сделать это 60 раз в секунду (1000 миллисекунд / 60.0).

    window.setInterval(draw, 1000 / 60.0);  

Далее, для каждого нашего рисования мы определяем, сколько времени прошло с момента последнего нарисованного кадра, и мы обновляем наш прогресс в направлении завершения анимации. Когда прогресс достигает завершения, мы меняем направление и уменьшаем его до нуля. Затем, когда мы достигаем нуля, мы поворачиваемся и снова движемся к одному. Таким образом, анимация просто постоянно перемещается назад и вперед.

time = new Date().getTime();  
elapsed = time - lastTime;  
lastTime = time;  
  
if (forward) {  
    progress += elapsed / duration;  
} else {  
    progress -= elapsed / duration;  
}  
if (progress > 1.0) {  
    progress = 1.0;  
    forward = false;  
}  
if (progress < 0) {  
    progress = 0;  
    forward = true;  
} 

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

b = 255.0 * progress;  
context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)"; 

И вот у нас есть эллипс, который оживляет в цвете.

Улучшение нашей анимации

Мы можем сделать небольшую корректировкук вышеупомянутой логике, чтобы улучшить способ выполнения анимации. Есть два предположения, которые делает вышеупомянутая логика, которые не обязательно верны: это предполагает, что машина, выполняющая логику, будет достаточно быстрой, чтобы обеспечить анимацию 60 кадров в секунду (fps), и что метод draw будет вызываться с надежной достаточно времени для создания плавного анимированного эффекта. Машина может быть слишком медленной, чтобы выполнить обновление анимации требуемое количество раз в секунду, и setInterval не гарантированно будет вызываться надежно вообще, особенно если много логики забивает очередь событий JavaScript. Некоторые современные браузеры поддерживают API, который позволяет нам лучше выполнять анимацию. По сути, мы можем запросить, чтобы браузер уведомил нас о готовности кадра анимации, чтобы мы могли отреагировать с помощью рендеринга некоторого контента.Это обеспечивает более надежную и плавную анимацию и должно позволить нам избегать попыток анимировать больше кадров, чем может обработать система. Вот модифицированная версия логики, которая будет использовать полизаполнение, чтобы использовать API requestAnimationFrame, если таковой имеется, и в противном случае использовать наш предыдущий метод для анимации, если мы в браузере, который не поддерживает requestAnimationFrame.

    function ensureQueueFrame() {  
        if (!window.queueFrame) {  
            if (window.requestAnimationFrame) {  
                window.queueFrame = window.requestAnimationFrame;  
            } else if (window.webkitRequestAnimationFrame) {  
                window.queueFrame = window.webkitRequestAnimationFrame;  
            } else if (window.mozRequestAnimationFrame) {  
                window.queueFrame = window.mozRequestAnimationFrame;  
            } else {  
                window.queueFrame = function (callback) {  
                    window.setTimeout(1000.0 / 60.0, callback);  
                };  
            }  
        }  
    }  
      
    $(function () {  
        var canv, context, lastTime, duration, progress, forward = true;  
        ensureQueueFrame();  
      
        $("#canvas").attr("width", "500px").attr("height", "500px");  
        canv = $("#canvas")[0];  
        context = canv.getContext("2d");  
      
        lastTime = new Date().getTime();  
      
        duration = 1000;  
        progress = 0;  
      
        function draw() {  
            var ellapsed, time, b;  
            queueFrame(draw);  
      
            time = new Date().getTime();  
            ellapsed = time - lastTime;  
            lastTime = time;  
      
            if (forward) {  
                progress += ellapsed / duration;  
            } else {  
                progress -= ellapsed / duration;  
            }  
            if (progress > 1.0) {  
                progress = 1.0;  
                forward = false;  
            }  
            if (progress < 0) {  
                progress = 0;  
                forward = true;  
            }  
      
            b = 255.0 * progress;  
            context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";  
            context.beginPath();  
            context.arc(150, 150, 120, 0, 2 * Math.PI, false);  
            context.fill();  
        }  
      
        queueFrame(draw);  
    });  

В следующий раз

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