JavaScript Canvas


Создание канваса

Создание на html-странице области рисования canvas размером 600x40 и вывод в её центре прямоугольника размером 100x20:

<html>
<head>
<style>
   canvas {background-color: #ffffff; border: 1px dashed black; }
</style>
</head>
<body>
<canvas id="canvas" width="600" height="40"></canvas>
<script>
   plot();
   function plot()
   {
      var canvas = document.getElementById('canvas');
      var ctx = canvas.getContext('2d');
      ctx.fillRect(canvas.width/2-15, canvas.height/2-10, 100, 20);
   }
</script>
</body>
</html>


Графические примитивы

Прямоугольники:

ctx.fillRect  (x,y, width,height);    // Рисует заполненный прямоугольник
ctx.strokeRect(x,y, width,height);    // Рисует границы прямоугольника
ctx.clearRect (x,y, width,height);    // Очищает заданную область и делает её полностью прозрачной
Настройки:
ctx.globalAlpha = 0.0                 // все сделать дальше прозрачным
 
ctx.fillStyle = "orange";             // разные способы задания цвета заливки
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";
 
ctx.strokeStyle = "orange";           // аналогично для цвета линий
ctx.setLineDash([5,7]);               // пунктирная линия с заданным параметром пунктира
 
ctx.lineWidth = 5;                    // толщина линии
сtx.lineCap   = "round";              // как оканчивать линию ['butt','round','square']
Линии:
beginPath();                          // начало рисования линии
...                                   // последовательность команд
closePath();                          // соединяет последнюю и первую точку (можно не делать)
stroke();                             // рисуем рамкой
fill();                               // и/или залитую фигуру
Возможные команды линий:
moveTo(x, y);                         // переместиться, ничего не рисуя
lineTo(x, y);                         // провести от последней точки линию
arc(x, y, radius, startAngle, endAngle, anticlockwise); // нарисовать дугу
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); // Безье кривые
var p = new Path2D("M 525,268 l 60,0  0,30 -60,0 0,-30 Z"); // кусок из svg-файла
ctx.stroke(p);                                              // M - move, L - сдвиг на

Примеры линий и окружности:
ctx.beginPath();
ctx.arc(200,  90, 5, 0, Math.PI*2, true);
ctx.stroke();
 
ctx.setLineDash([5,7]);
ctx.beginPath();
   ctx.moveTo(10,30);
   ctx.lineTo(200,90);
   ctx.lineTo(200,5);
ctx.stroke();
   
Кружки - точки контроля линии Безье:
ctx.beginPath();
   ctx.moveTo(10,130);
   ctx.bezierCurveTo(50,190, 159,70, 200,130);
   ctx.lineTo(200,190);
   ctx.lineTo(10,190);
ctx.closePath();
 
ctx.fillStyle = 'red';
ctx.fill();               // заливаем
 
ctx.lineWidth = 4;
ctx.strokeStyle = 'green';
ctx.stroke();             // делаем обводку
   
Градиент закрашивается в той же системе координат, какой рисуется фигура, а не внутренних координат фигуры. Если мы изменим x,y фигуры на 100,100, то градиент будет по-прежнему находиться в начале экрана.
var grad = ctx.createLinearGradient(0,0,200,0);
grad.addColorStop(0, "white");
grad.addColorStop(0.5, "red");
grad.addColorStop(1, "black");
ctx.fillStyle = grad;
ctx.fillRect(0,0,400,50);
   

Текст

ctx.font = "italic 30pt Arial";       // шрифт текста
ctx.font         = "8pt Consolas";    // моноширинный шрифт
 
ctx.fillStyle   = "#00F";             // цвет заливки
ctx.strokeStyle = "#F00";             // цвет обводки
 
ctx.textAlign    = "center";          // центрован по горизонтали (start,end,left,right)
ctx.textBaseline = "middle";          // и по вертикали (top,hanging,alphabetic,ideographic,bottom)
 
ctx.fillText  ("Fill   txt", 20,50);  // залитый текст
ctx.strokeText("Stroke txt", 20,70);  // текст по контуру (обводка)
 
ctx.strokeStyle = "#F00";             // текст с тенью
ctx.shadowColor = "#F00";
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur    = 5;
ctx.strokeText("Shadow text", 20, 100);
 
var grad = ctx.createLinearGradient(0,0,0,60);  // текст с градиентом цвета
grad.addColorStop(0.0, 'rgba(0, 0, 255, 1)');
grad.addColorStop(0.3, 'rgba(128, 0, 255, 0.6)');
grad.addColorStop(0.6, 'rgba(0, 0, 255, 0.4)');
grad.addColorStop(1.0, 'rgba(0, 255, 0, 0.2)');
ctx.fillStyle = grad;
 
var pat = ctx.createPattern(img, 'repeat');       // заливка текста текстурой
ctx.fillStyle = pat;

Пример функции, которая разбивает текст на строки, чтобы он поместился в ящике.

// Вывод текста внутри ящика шириной maxWidth, перенося его по словам,
// считая, что выстота строки равна lineHeight
function wrapText(context, text, x, y, maxWidth, lineHeight)
{
   var words = text.split(" ");
   var countWords = words.length;
   var line = "";
   for (var n = 0; n < countWords; n++) {
      var testLine = line + words[n] + " ";
      var testWidth = context.measureText(testLine).width;
      if (testWidth > maxWidth) {
         context.fillText(line, x, y);
         line = words[n] + " ";
         y += lineHeight;
      }
      else {
         line = testLine;
      }
   }
   context.fillText(line, x, y);
}


Картинки

Загрузка картинки:

var img = new Image();      // Создаём новый объект Image
img.src = 'picture.png';    // Устанавливаем путь к источнику
Если к моменту вызова drawImage загрузка ещё не закончилась, скрипт "подвисает" до её окончания. Чтобы избежать этого, используйте обработчик события onload:
var img = new Image();   // Создать новый объект Image
img.src = 'myImage.png'; // Установить путь к источнику
img.onload = function(){ /* выполнить drawImage здесь */   }
В обработчике img.onload можно, например, написать img.ok=true и рисовать картинку, только если определено это поле.

Собственно рисование картинки:

ctx.drawImage(img, dx, dy);
ctx.drawImage(img, dx, dy, dWidth, dHeight);
ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Пример загрузчика картинок
var imageList = [              // список картинок для загрузки
   {src: "pic/boy.jpg"},       // 0
   {src: "pic/girl.jpg"},      // 1
];
var imageLoadOkTot  = 0;       // число успешно загруженных картинок
var imageLoadErrTot = 0;       // число ошибок при загрузке картинок
var imageLoadErrStr = "";      // текстовая строка с содержанием ошибки загрузки
 
function init()
{
   for(var i in imageList){                         // готовим картинки к загруке
      imageList[i].img = new Image();
      imageList[i].ok=false;
      imageList[i].img.onload  = function(){        // вызывается когда картинка загружена
         imageList[i].ok=true;  imageLoadOkTot++;
      }
      imageList[i].img.onerror = function (evt) {   // вызывается при ошибке загрузки картинки
         imageLoadErrTot++;
         imageLoadErrStr += "["+this.src+":"+evt.type+"]";
      }
      imageList[i].img.src = imageList[i].src;
   }
}

Буферы пикселей

Шахматная доска:
var data = ctx.createImageData(100,100); // создаём буфер 100x100 пикселей
 
for(var x=0; x < data.width; x++) {      // обходим в цикле каждый пиксель
   for(var y=0; y < data.height; y++) {
      var val = 0;
      var h = (Math.floor(x/4)%2 === 0); // цикл каждые 4 пикселя
      var v = (Math.floor(y/4)%2 === 0); // цикл каждые 4 пикселя
      if( (h && !v) || (!h && v)) val = 255; else  val = 0;
 
      var index = (y*data.width+x)*4;    // вычисляем индекс
      data.data[index]   = val;          // красный
      data.data[index+1] = val;          // зелёный
      data.data[index+2] = val;          // синий
      data.data[index+3] = 255;          // явно задаём альфу как 255
  }
}
ctx.putImageData(data,0,0);              // устанавливаем данные обратно
Инверсия фото:
var img = new Image();
img.onload = function()
{
   ctx.drawImage(img, 0, 0); // рисуем изображение на холсте
 
   // получаем данные холста
   var data = ctx.getImageData(0,0, img.width,img.height);
 
   for(var n=0; n < data.width*data.height; n++){ // инвертируем
      var index = n*4;
      data.data[index]   = 255-data.data[index];
      data.data[index+1] = 255-data.data[index+1];
      data.data[index+2] = 255-data.data[index+2];
      // не трогайте альфу
   }
   ctx.putImageData(data,0,100); // возвращаем данные обратно
}
img.src = "andromeda_galaxy.jpg";

В Google Chrome по умолчанию чтение с диска локальных файлов запрещно. По этой причине существуют проблемы с getImageData, если на канвасе нарисована картинка. Чтобы это обойти, необходимо все Chrome-ы закрыть, а потом запустить с параметром --allow-file-access-from-files (на иконке правая кнопка мыши, Properties и в поле Target добавляем этот параметр).


Матрицы

Преобразования, это последовательный перенос, поворот и скейлинг канваса с последующим рисованием его на "исходном" канвасе:
ctx.save();
ctx.translate(canvas.width/2, 0);  // сдвинули канвас вдоль оси Х
ctx.strokeStyle = "red";   ctx.strokeRect(0,0, 100,50);
ctx.rotate(30 * Math.PI / 180);    // поворачиваем
ctx.strokeStyle = "green"; ctx.strokeRect(0,0, 100,50);
ctx.scale(0.5,0.75);               // растягиваем
ctx.strokeStyle = "blue";  ctx.strokeRect(0,0, 100,50);
ctx.restore();
ctx.save();
ctx.translate(canvas.width/2, 0);  // сдвинули канвас вдоль оси Х
ctx.strokeStyle = "red";   ctx.strokeRect(0,0, 100,50);
ctx.scale(0.5,0.75);               // растягиваем
ctx.strokeStyle = "green"; ctx.strokeRect(0,0, 100,50);
ctx.rotate(30 * Math.PI / 180);    // поворачиваем
ctx.strokeStyle = "blue";  ctx.strokeRect(0,0, 100,50);
ctx.restore();
Пример: Крутилка

В центре канваса врашается линия на концах которой находятся также вращающиеся квадратики:

var canvas;                         // элемент id="canvas"
var ctx;                            // контекст рисования
var angle = 0, box1X=-50, box2X=50; // угол поворота и положения центров ящиков
 
/*******************************************************************************
 * запускается при загрузке страницы
 */
function run()
{
   canvas = document.getElementById('canvas');     // элемент id="canvas"
   ctx = canvas.getContext('2d');                  // контекст рисования
   setInterval(rotate, 100);                       // вызываем через 100ms
}
/*******************************************************************************
 * вызывается таймером каждые 100ms
 */
function rotate()
{
   ctx.clearRect(0,0, canvas.width,canvas.height);  // очищаем канвас
 
   ctx.save();
   ctx.translate(canvas.width/2, canvas.height/2);  // камеру в центр канваса
   ctx.strokeRect(-5,-5, 10, 10);                   // там (относит.центра) квадрат 10x10
   ctx.scale(2,2);                                  // увеличиваем всё дальше в 2 раза
   ctx.rotate(angle * Math.PI / 180);               // и поворачиваем на угол angle
 
   ctx.beginPath();                                 // линия, соединяющая ящики
   ctx.moveTo(box1X, 0);   ctx.lineTo(box2X, 0);    // она будет вращаться на угол angle
   ctx.closePath();                                 // вокруг центра канваса
   ctx.stroke();
 
   ctx.fillStyle = "blue";
   box(box1X, 0, 50, 10, 1,1, 10*angle);            // ящики вращаются с линией
   box(box2X, 0, 50, 10, 2,2, 10*angle);            // и ещё поворачиваются вокруг свомх осей
 
   ctx.restore();
   angle+=1;                                        // увеличиваем на 1 градус
}
/*******************************************************************************
 * Рисует прямоугольник шириной w и высотой h.
 * Его _центр_  находится в x,y относительно _родителя_
 * и повёрнут на угол "a" в градусах. Кроме этого отмаштабирован на sx, sy
 */
function box(x,y, w, h, sx, sy,  a)
{
   ctx.save();                                       // сохраняем параметры канваса
   ctx.translate(x,y);                               // перемещаем камеру в x,y
   ctx.scale(sx, sy);
   ctx.rotate(a*Math.PI/180);                        // поворачиваем на угол 10*angle
   ctx.fillRect(-w/2, -h/2, w, h);                   // помещаем центр в x,y
   ctx.restore();                                    // восстанавливаем параметры канваса
}


События

var canvas;                         // элемент id="canvas"
var ctx;                            // контекст рисования
/*******************************************************************************
 * запускается при загрузке страницы
 */
function run()
{
   canvas = document.getElementById('canvas');     // элемент id="canvas"
   ctx = canvas.getContext('2d');                  // контекст рисования
 
   canvas.addEventListener("mousedown", mouseDn, false);
   canvas.addEventListener("mouseup",   mouseUp, false);
   canvas.addEventListener("mousemove", mouseMv, false);
 
   setInterval(timer, 100);                       // вызываем через 100ms
}
/*******************************************************************************
 * вызывается при нажатии на мышь
 */
function mouseDn(event)
{
   var rct = canvas.getBoundingClientRect();
   var x = event.clientX - rct.left;
   var y = event.clientY - rct.top;
 
   ctx.fillText(x+","+y, x+10, y+10);
}
 
/*******************************************************************************
 * вызывается таймером каждые 100ms
 */
function timer()
{
   //ctx.clearRect(0,0, canvas.width,canvas.height);  // очищаем канвас
}

SVG

Файл векторной графики (см. детали на w3schools):

<svg width="600" height="400">
   <circle cx="116" cy="67" r="24" stroke="black" fill="#dddddd"></circle>
   <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0); stroke-width:2"></line>
   <text x="0" y="15" fill="red">I love SVG!</text>
   Sorry, your browser does not support inline SVG.
</svg>
Справочник по svg-формату см. здесь.


См. также