§10. Графика и JavaScript. Тэг canvas.

Учимся программировать вместе с JavaScript
Глава I. Обо всём понемногу.

§10. Графика и JavaScript. Тэг canvas.

Тэг canvas. Метод getContext('2d') и объект CanvasRenderingContext2D. Формирование изображения на экране компьютера. Термин «перо». Методы для рисования линий. Настройка пера. Путь пера. Метод beginPath(). Демонстрация того, что пиксели имеют координаты с половинками. Вывод графика функций. Стирание рисунка.

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

Поэтому, заканчивая главу «Обо всём по не многу», мы познакомимся с тем, каким образом можно выводить графические изображения с помощью JavaScript. 

 

Тэг canvas. 

Для того, что бы рисовать с помощью JavaScript существует тэг canvas. В коде html документа он может выглядеть следующим образом: 

 

<canvas id = "image_1" width = "100px" height = "100px" style = "border:solid 2px black"></canvas> 

 

В окне браузера данный html код отобразится так: 

 

 

Атрибут id нужен, как вы уже знаете, для доступа к элементу в JavaScript коде. Атрибуты width и height задают ширину и высоту прямоугольной области. Атрибут style не обязателен, в данном случае он просто создаёт рамку для того, что бы было наглядней. 

Обратите внимание, что закрывающий тэг </canvas> необходим обязательно. 

Примечание: в старых версиях IE тэг canvas не работает. На данном этапе этот вопрос рассматриваться не будет. Просто используйте любой другой браузер. 

 

Метод getContext('2d') и объект CanvasRenderingContext2D. 

Все методы и свойства для рисования находятся в объекте CanvasRenderingContext2D. Для того, что бы ими пользоваться, необходимо получить доступ к этому объекту. Делается это с помощью метода getContext('2d') объекта, который отражает объект элемента canvas. Как было уже сказано, если в тэге есть атрибут id, то в коде JavaScript автоматически появляется объект с таким же именем, как значение этого атрибута. Так и в нашем случае у нас есть объект image_1, который отражает объект canvas, и у которого есть метод getContext('2d'). В качестве аргумента этому метода необходимо передать строку '2d'. Строка '2d' говорит о том, что рисование будет происходить в двумерной плоскости. После вызова метода getContext('2d'), он возвращает ссылку на объект CanvasRenderingContext2D. Эту ссылку необходимо присвоить какой-либо переменной, после чего через неё будут доступны все методы и свойства объекта CanvasRenderingContext2D. В коде это выглядит следующим образом: 

 

<script> 

var im_1 = image_1.getContext('2d'); 

</script> 

 

Возможно, всё, что было только что сказано, на данном этапе, звучит не совсем ясно. Предлагаю принять на веру и просто знать, что необходимо написать данную строчку кода, где image_1 - значение атрибута id тэга canvas, а im_1 - переменная объект, которая будет содержать в себе все методы и свойства для рисования. 

 

После того, как тэг canvas добавлен в html документ, и получена переменная объект со свойствами и методами для рисования, можно приступать непосредственно к рисованию. Прежде чем продолжить рассмотрим ещё вопрос о том, как формируется изображение на экране компьютера. 

 

Формирование изображения на экране компьютера. 

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

Вся область, созданная тэгом canvas, поделена на точки (пиксели), которые имеют свои собственные координаты. Отчёт точек начинается с верхнего левого угла. Для того, что бы было понятно можно условно провести с лева направо ось Х, и сверху вниз ось Y. Наглядно это демонстрирует следующий рисунок, на котором изображена буква Т: 

 

 

 

На рисунке закрашенными в чёрный цвет являются пиксели со следующими координатами: (1.5; 1.5), (2.5; 1.5), (3.5; 1.5), (3.5; 2.5) и т.д. Здесь стоит уточнить: в JavaScript при рисовании в тэге canvas координаты пикселей имеют не целые значения, как в большинстве языков, а с половинками. Поэтому для рисования координаты можно задавать вещественными числами. Далее данное обстоятельство будет продемонстрировано. 

 

Термин «перо». 

Прежде чем приступить к рассмотрению свойств и методов для рисования договоримся об использовании термина «перо». Под ним мы будем понимать абстрактное понятие, как будто мы будем рисовать виртуальным пером, давая ему команды с помощью методов. Так же устанавливая свойства пера, мы будем задавать его толщину, цвет и т.д. 

 

Методы для рисования линий. 

Для того, что бы нарисовать линию необходимо вызвать три метода: moveTo(x,y); lineTo(x,y); stroke(). 

Метод moveTo(x1,y1) устанавливает перо в начальное положение с координатами x1 и y1. Метод lineTo(x2,y2) даёт команду перу нарисовать отрезок из того положения где оно находится до точки с координатами x2 и y2. Причём после завершения работы этого метода перо будет находиться уже в точке (x2,y2). 

При повторном вызове метода lineTo(x3,y3) перо нарисует линию из точки (x2,y2) в точку (x3,y3). Для того, что бы начать рисовать из новой точки, необходимо установить перо в эту точку методом moveTo. 

Здесь, необходимо сказать о том, что рисование этими методами происходит не в окне браузера, а только в памяти компьютера, т.е. после их вызовов в окне браузера вы ни чего не увидите. Для того, что бы нарисованное ими отобразилось необходимо вызвать метод stroke(). Пример: 

 

<!DOCTYPE html> 

<html> 

<body> 

<canvas id = "image_1" width = "400px" height = "200px" style = "border:solid 2px black"></canvas> 

 

<script> 

var im_1 = image_1.getContext('2d'); 

 

//Рисуем большой прямоугольник 

im_1.moveTo(10,10); 

im_1.lineTo(390,10); 

im_1.lineTo(390,190); 

im_1.lineTo(10,190); 

im_1.lineTo(10,10); 

 

//Рисуем маленький прямоугольник 

im_1.moveTo(100,50); 

im_1.lineTo(300,50); 

im_1.lineTo(300,150); 

im_1.lineTo(100,150); 

im_1.lineTo(100,50); 

 

//Отображаем нарисованное в окне браузера 

im_1.stroke(); 

</script> 

</body> 

</html> 

 

 

Настройка пера. 

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

  • strokeStyle - устанавливает цвет, в качестве значения необходимо присвоить строку с цветом как в CSS; 

  • lineWidthтолщина линии в пикселях. 

Эти свойства можно изменить в любом месте кода до вызова метода stroke. Данный метод выводит в окно браузера линии с учётом этих свойств. Добавим изменение этих свойств в предыдущий пример: 

 

................ 

//Настраиваем свойства линий 

im_1.lineWidth = 2; 

im_1.strokeStyle = 'blue'; 

//Отображаем нарисованное в окне браузера 

im_1.stroke(); 

 

 

Путь пера. Метод beginPath(). 

Введём ещё одно абстрактное понятие – путь пера. Когда мы вызываем методы moveTo и lineTo, перо проделывает определённые движения. Все они в совокупности формируют так называемый путь пера. Когда мы вызываем метод stroke, то в окно браузера как раз отображается весь путь с текущими свойствами. Поэтому в одном пути невозможно нарисовать линии различными цветами и толщиной. 

Для того, что бы закончить путь и начать новый существует метод beginPath(). После его вызова путь начинается сначала. 

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

 

<script> 

var im_1 = image_1.getContext('2d'); 

//Рисуем большой прямоугольник 

//Настраиваем свойства линий 

im_1.lineWidth = 4; 

im_1.strokeStyle = 'red'; 

//Непосредственно рисуем 

im_1.moveTo(10,10); 

im_1.lineTo(390,10); 

im_1.lineTo(390,190); 

im_1.lineTo(10,190); 

im_1.lineTo(10,10); 

//Обображаем нарисованное в окне браузера 

im_1.stroke(); 

 

//Рисуем маленький прямоугольник 

im_1.beginPath();//Начали новый путь 

//Непосредственно рисуем 

im_1.moveTo(100,50); 

im_1.lineTo(300,50); 

im_1.lineTo(300,150); 

im_1.lineTo(100,150); 

im_1.lineTo(100,50); 

//Настраиваем свойства линий 

im_1.lineWidth = 2; 

im_1.strokeStyle = 'blue'; 

//Обображаем нарисованное в окне браузера 

im_1.stroke(); 

</script> 

 

 

Демонстрация того, что пиксели имеют координаты с половинками. 

Теперь, как было обещано, продемонстрируем тот факт, что пиксели имеют координаты с половинками. Для этого нарисуем две параллельные оси X линии: одна будет иметь по оси Y координату 10, другая 20.5. Обе они будут иметь толщину в один пиксель и чёрный цвет: 

 

im_1.lineWidth = 1; 

im_1.strokeStyle = 'black'; 

im_1.moveTo(0,10); 

im_1.lineTo(400,10); 

im_1.stroke(); 

 

im_1.beginPath();//Начали новый путь 

im_1.lineWidth = 1; 

im_1.strokeStyle = 'black'; 

im_1.moveTo(0,20.5); 

im_1.lineTo(400,20.5); 

im_1.stroke(); 

 

 

Как видно из результата, первая линия толще второй. Это потому, что вторая нарисована толщиной точно в один пиксель, а первая получилась – в два пикселя. Это потому, что первая лини имеет координату по оси Y между пикселями. Поэтому она нарисована на предыдущем и следующем пикселях. 

 

Вывод графика функций. 

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

Думаю, что нарисовать оси координат вы уже можете сами. Далее пример того, какими они могут получиться: 

 

<!DOCTYPE html> 

<html> 

<body> 

<canvas id = "image_1" width = "400px" height = "200px" style = "border:solid 2px black"></canvas> 

 

<script> 

var g = image_1.getContext('2d'); 

g.lineWidth = 1; 

g.strokeStyle = 'black'; 

 

//Ось Y 

g.moveTo(10.5,194.5); 

g.lineTo(10.5,5.5); 

g.lineTo(7.5,13.5); 

g.moveTo(10.5,5.5); 

g.lineTo(13.5,13.5); 

 

//Ось X 

g.moveTo(5.5,100.5); 

g.lineTo(394.5,100.5); 

g.lineTo(386.5,97.5); 

g.moveTo(394.5,100.5); 

g.lineTo(386.5,103.5); 

 

g.stroke(); 

 

</script> 

</body> 

</html> 

 

 

Принцип рисования самой функции заключается в том, что мы входим в цикл и для каждого нового значения X вычисляем значение Y, после чего отправляем перо в эту точку с помощью метода lineTo(X,Y). После выхода из цикла вырисовываем весь путь пера с помощью метода stroke(). Звучит не совсем сложно, однако есть ряд нюансов. 

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

Во-вторых, нам необходимо, что бы функция имела определённый размер, который сочетался бы с осями координат. Речь идёт о масштабе. Соответственно нам необходимо координату каждой точки умножить на определённое число. 

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

Теперь пример, в котором с учётом перечисленных нюансов выведен график sin(x): 

 

//Выводим функцию 

g.beginPath(); 

g.lineWidth = 2; 

g.strokeStyle = 'red'; 

 

g.moveTo(10,100); 

var x = 0, y = 0;//Координаты точек функции 

var m = 30;//Масштаб 

var x0 = 10.5, y0 = 100.5;//Координаты начальной точки 

 

for (x = 0; x<((400-x0*2)/m); x = x+1/m) 

{ 

  y = Math.sin(x); 

  g.lineTo(x*m+x0,y*m+y0); 

} 

g.stroke(); 

 

 

 

Стирание рисунка. 

Последнее, что нам понадобиться для решения задач – это научиться стирать рисунок. Для этого достаточно изменить ширину или высоту области для рисования за счёт изменения свойства width или height соответственно. Причём можно оставить и прежние значения, главное написать строчку кода. Для нашего примера с функцией эта строчка будет выглядеть следующим образом: 

 

image_1.width = 400; 

 

Задачи. 

1. Вывести график какой-либо функции. Добавить кнопки «увеличить» и «уменьшить», при нажатии на которые график будет увеличиваться или уменьшаться соответственно. Для этого необходимо старый рисунок стереть и нарисовать новый с новым масштабом. 

2. Создать функцию рисования штрих-пунктирной линий. 

3. Вывести на экран чертеж, представленный на рисунке (вместо чертежа можете нарисовать какой-либо предмет по выбору, например машинку, дом и т.д.). Добавить кнопки «Увеличить» и «Уменьшить», увеличивающие и уменьшающие чертёж соответственно. Так же добавить кнопки «Влево» и «Вправо», которые будут перемещать предмет по экрану. 

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

 

img16.png 

 

4. Создать объект char, методы которого будут рисовать буквы. Примечание: буквы должны быть вписаны в невидимый прямоугольник одного размера. Причём при увеличении его размеров буквы должны автоматически увеличиваться. Вывести с помощью получившегося объекта слово. С помощью двух кнопок его увеличивать и уменьшать. 

 

Примеры решений. 

1. 

<!DOCTYPE html> 

<html> 

<body> 

<canvas id = "image_1" width = "400px" height = "200px" style = "border:solid 2px black"></canvas> 

<br> 

<input type = "button" value = "Увеличить" onclick = "increase()"> 

<input type = "button" value = "Уменьшить" onclick = "decrease()"> 

 

<script> 

var m = 30;//Масштаб 

var x0 = 10.5, y0 = 100.5;//Координаты начальной точки 

var g = image_1.getContext('2d'); 

 

function drawF()//Рисует график функции 

{ 

  //Рисуем оси 

  g.lineWidth = 1; 

  g.strokeStyle = 'black'; 

 

  //Ось Y 

  g.moveTo(10.5,194.5); 

  g.lineTo(10.5,5.5); 

  g.lineTo(7.5,13.5); 

  g.moveTo(10.5,5.5); 

  g.lineTo(13.5,13.5); 

 

  //Ось X 

  g.moveTo(5.5,100.5); 

  g.lineTo(394.5,100.5); 

  g.lineTo(386.5,97.5); 

  g.moveTo(394.5,100.5); 

  g.lineTo(386.5,103.5); 

 

  g.stroke(); 

 

  //Выводим функцию 

  g.beginPath(); 

  g.lineWidth = 2; 

  g.strokeStyle = 'red'; 

 

  g.moveTo(10,100); 

  var x = 0, y = 0;//Координаты точек функции 

  for (x = 0; x<((400-x0*2)/m); x = x+1/m) 

  { 

    y = Math.sin(x); 

    g.lineTo(x*m+x0,y*m+y0); 

  } 

  g.stroke(); 

} 

 

//Нажатие на кнопку увеличить 

function increase() 

{ 

  image_1.width = 400;//Стираем старое изображение 

  m = m+1;//Меняем масштаб 

  drawF();//Перерисовываем в новом масштабе 

} 

 

//Нажатие на кнопку уменьшить 

function decrease() 

{ 

  image_1.width = 400;//Стираем старое изображение 

  m = m-1;//Меняем масштаб 

  drawF();//Перерисовываем в новом масштабе 

} 

 

//Рисуем график функции 

drawF(); 

 

</script> 

</body> 

</html>