Журнал LinuxFormat - перейти на главную

LXF94:Графическое web-приложение

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(Взаимодействие с холстом)
м (категория)
 
(не показаны 8 промежуточных версий 3 участников)
Строка 12: Строка 12:
 
Чтобы работать с тэгом canvas, достаточно создать небольшой HTML-файл и открыть его в Firefox. Для начала рассмотрим несложный пример. Тэг canvas добавляется на страницу обычным образом. Атрибуты «ширина» и «высота» говорят сами за себя; но вы также можете включить стандартные HTML-атрибуты, типа class, id, style и других:
 
Чтобы работать с тэгом canvas, достаточно создать небольшой HTML-файл и открыть его в Firefox. Для начала рассмотрим несложный пример. Тэг canvas добавляется на страницу обычным образом. Атрибуты «ширина» и «высота» говорят сами за себя; но вы также можете включить стандартные HTML-атрибуты, типа class, id, style и других:
  
  <canvas id=”canvas” style=”border : thin solid black;
+
<source lang=html4strict>
  width=”600” height=”400”></canvas>
+
  <canvas id="canvas" style="border : thin solid black;"
 +
  width="600" height="400"></canvas>
 +
</source>
  
 
Добавьте этот кусок кода на пустую HTML-страницу и откройте ее в браузере с помощью File > Open (Файл > Открыть). Все, что вы видите – это пустая область с границей, так как наш код устанавливает тэг canvas, но ничего другого не делает: просто обозначает пустой холст
 
Добавьте этот кусок кода на пустую HTML-страницу и откройте ее в браузере с помощью File > Open (Файл > Открыть). Все, что вы видите – это пустая область с границей, так как наш код устанавливает тэг canvas, но ничего другого не делает: просто обозначает пустой холст
Строка 20: Строка 22:
 
Самое интересное начинается в JavaScript. JS используется для рисования линий, кривых, областей и изображений на объекте canvas. Чтобы создать две линии, добавьте это в только что созданный файл и обновите страницу в Firefox:
 
Самое интересное начинается в JavaScript. JS используется для рисования линий, кривых, областей и изображений на объекте canvas. Чтобы создать две линии, добавьте это в только что созданный файл и обновите страницу в Firefox:
  
  <canvas id=”canvas” width=”600” height=”400” style=”background-color : #c0c0c0;></canvas>
+
<source lang=html4strict>
 +
  <canvas id="canvas" width="600" height="400" style="background-color : #c0c0c0;"></canvas>
 
  <script>
 
  <script>
  var canvas = document.getElementById(‘canvas’);
+
  var canvas = document.getElementById('canvas');
  ctx = canvas.getContext(‘2d’);
+
  ctx = canvas.getContext('2d');
 
  ctx.lineTo(100, 100);
 
  ctx.lineTo(100, 100);
 
  ctx.lineTo(200, 300);
 
  ctx.lineTo(200, 300);
Строка 30: Строка 33:
 
  ctx.stroke();
 
  ctx.stroke();
 
  </script>
 
  </script>
 +
</source>
  
Давайте разберемся. Сначала мы получаем HTML-элемент (или узел) по id ‘canvas’. Затем из элемента canvas мы получаем контекстный объект, его мы рассмотрим далее. Объект используется для собственно рисования.
+
Давайте разберемся. Сначала мы получаем HTML-элемент (или узел) по id 'canvas'. Затем из элемента canvas мы получаем контекстный объект, его мы рассмотрим далее. Объект используется для собственно рисования.
  
 
Вот и все, что необходимо для применения тэга canvas: HTML-тэг и немного JavaScript. Вся ваша разработка может вестись с помощью Firefox и тестироваться вне сервера, так как логика и рисование находится на стороне клиента. Но что мы можем? Чтобы подстрекнуть свое
 
Вот и все, что необходимо для применения тэга canvas: HTML-тэг и немного JavaScript. Вся ваша разработка может вестись с помощью Firefox и тестироваться вне сервера, так как логика и рисование находится на стороне клиента. Но что мы можем? Чтобы подстрекнуть свое
Строка 45: Строка 49:
 
Контекст – это объект, содержащий функции для рисования линий, блоков цветных изображений и так далее. Линии он называет штрихами [stroke], а цветовые блоки – заливкой [fill]:
 
Контекст – это объект, содержащий функции для рисования линий, блоков цветных изображений и так далее. Линии он называет штрихами [stroke], а цветовые блоки – заливкой [fill]:
  
  var canvas = document.getElementById(‘canvas’);
+
<source lang=html4strict>
  ctx = canvas.getContext(“2d”);
+
  var canvas = document.getElementById('canvas');
 +
  ctx = canvas.getContext("2d");
 
  ctx.strokeRect(50, 50, 100, 100);
 
  ctx.strokeRect(50, 50, 100, 100);
 
  ctx.stroke();
 
  ctx.stroke();
 
  ctx.fillRect(100, 100, 100, 100);
 
  ctx.fillRect(100, 100, 100, 100);
 
  ctx.fill();
 
  ctx.fill();
 +
</source>
  
 
Большая часть функций понятна: strokeRect() рисует прямоугольник, fillRect() его заполняет. У strokeRect() и fillRect() похожие параметры – x и y верхнего левого угла прямоугольника, а также ширина и высота. Canvas ведет отсчет от левого верхнего угла, то есть 50, 50 означает 50 пикселей слева и 50 пикселей сверху. Тем, кто привык мыслить в терминах координат обычных графиков, тут легко и запутаться! Заметим также, что нам надо вызывать функции .stroke() и .fill(). Не забудьте это сделать, иначе останетесь с пустым холстом.
 
Большая часть функций понятна: strokeRect() рисует прямоугольник, fillRect() его заполняет. У strokeRect() и fillRect() похожие параметры – x и y верхнего левого угла прямоугольника, а также ширина и высота. Canvas ведет отсчет от левого верхнего угла, то есть 50, 50 означает 50 пикселей слева и 50 пикселей сверху. Тем, кто привык мыслить в терминах координат обычных графиков, тут легко и запутаться! Заметим также, что нам надо вызывать функции .stroke() и .fill(). Не забудьте это сделать, иначе останетесь с пустым холстом.
  
Цвет линий и заливки устанавливается на холсте с помощью lineStyle и fillStyle, которые принимают похожие аргументы. Самый простой способ ввести цвет – указать его: ctx.lineStyle=’red’ – или обозначить шестнадцатеричным числом: ctx.lineStyle=#a0b0c0’;. Для получения прозрачности предусмотрен альфа-канал в функции rgba(), принимающей четыре параметра: красный, зеленый, синий и альфа.
+
Цвет линий и заливки устанавливается на холсте с помощью lineStyle и fillStyle, которые принимают похожие аргументы. Самый простой способ ввести цвет – указать его: ctx.lineStyle='red' – или обозначить шестнадцатеричным числом: ctx.lineStyle='#a0b0c0';. Для получения прозрачности предусмотрен альфа-канал в функции rgba(), принимающей четыре параметра: красный, зеленый, синий и альфа.
 
Например:
 
Например:
  
  ctx.lineStyle = ‘red’;
+
<source lang=html4strict>
  ctx.fillStyle = ‘rgb(200, 100, 0, 0.5);
+
  ctx.lineStyle = 'red';
 +
  ctx.fillStyle = 'rgb(200, 100, 0, 0.5)';
 +
</source>
  
 
Когда две линии пересекаются, стиль их сочленения можно выбрать с помощью lineJoin:
 
Когда две линии пересекаются, стиль их сочленения можно выбрать с помощью lineJoin:
  
  ctx.lineJoin = ‘curve’;
+
<source lang=html4strict>
 +
  ctx.lineJoin = 'curve';
 +
</source>
  
 
Две последние важные концепции – перенос и поворот. Перенос – это передвижение в другую точку холста; поворот, очевидно, и есть поворот холста. Оба действия производятся до того, как вы начнете рисовать. Например, если вы хотите нарисовать прямоугольник под 45°, то сначала вам надо добавить функцию поворота:
 
Две последние важные концепции – перенос и поворот. Перенос – это передвижение в другую точку холста; поворот, очевидно, и есть поворот холста. Оба действия производятся до того, как вы начнете рисовать. Например, если вы хотите нарисовать прямоугольник под 45°, то сначала вам надо добавить функцию поворота:
  
 +
<source lang=html4strict>
 
  ctx.rotate(-45 * Math.PI / 180);
 
  ctx.rotate(-45 * Math.PI / 180);
 
  ctx.strokeRect(50, 50, 100, 100);
 
  ctx.strokeRect(50, 50, 100, 100);
 
  ctx.stroke();
 
  ctx.stroke();
 +
</source>
  
 
Все эти настройки внутри холста, то есть lineStyle, fillStyle, поворот и перенос, можно сохранять в стеке состояний, а потом брать их оттуда. Это позволяет понаделать кучу стилевых настроек, а затем разом их отменить, закончив рисование:
 
Все эти настройки внутри холста, то есть lineStyle, fillStyle, поворот и перенос, можно сохранять в стеке состояний, а потом брать их оттуда. Это позволяет понаделать кучу стилевых настроек, а затем разом их отменить, закончив рисование:
  
 +
<source lang=html4strict>
 
  ctx.save(); // Сохранить текущее состояние, чтобы мы могли его восстановить
 
  ctx.save(); // Сохранить текущее состояние, чтобы мы могли его восстановить
 
  ctx.rotate(-45 * Math.PI / 180);
 
  ctx.rotate(-45 * Math.PI / 180);
  ctx.lineStyle = ‘blue’;
+
  ctx.lineStyle = 'blue';
 
  ctx.strokeRect(50, 50, 100, 100); // нарисовать прямоугольник
 
  ctx.strokeRect(50, 50, 100, 100); // нарисовать прямоугольник
 
  ctx.stroke();
 
  ctx.stroke();
 
  ctx.restore(); // восстановить lineStyle и поворот
 
  ctx.restore(); // восстановить lineStyle и поворот
 +
</source>
  
 
Функции save и restore очень полезны, если вы производите много переносов и поворотов: они избавят вас от необходимости держать в памяти всю серию изменений.
 
Функции save и restore очень полезны, если вы производите много переносов и поворотов: они избавят вас от необходимости держать в памяти всю серию изменений.
Строка 85: Строка 99:
 
Чтобы создать коллекцию фигур-шаблонов, заготовим пару полезных объектов: обертку холста и обертку фигуры. Обертка фигуры представляет наибольший интерес – она инкапсулирует различные сложные фигуры. Обертка холста будет следить за размещением фигур на холсте.
 
Чтобы создать коллекцию фигур-шаблонов, заготовим пару полезных объектов: обертку холста и обертку фигуры. Обертка фигуры представляет наибольший интерес – она инкапсулирует различные сложные фигуры. Обертка холста будет следить за размещением фигур на холсте.
  
 +
<source lang=html4strict>
 
  function draw() {
 
  function draw() {
   ic = new iCanvas(‘canvas’);
+
   ic = new iCanvas('canvas');
 
   // добавьте здесь все свои фигуры
 
   // добавьте здесь все свои фигуры
 
   setupInteraction();
 
   setupInteraction();
Строка 105: Строка 120:
 
   renderShape : function() { },
 
   renderShape : function() { },
 
  }
 
  }
 +
</source>
  
 
Полная версия исходного кода доступна по адресу http://www.linuxformat.co.uk/mag/canvas.htm, но единственной важной частью является renderShape(), так как все, что мы будем туда писать, будет родным кодом холста – другие функции (например, drawShape()) добавляются
 
Полная версия исходного кода доступна по адресу http://www.linuxformat.co.uk/mag/canvas.htm, но единственной важной частью является renderShape(), так как все, что мы будем туда писать, будет родным кодом холста – другие функции (например, drawShape()) добавляются
Строка 111: Строка 127:
 
JavaScript сопровождается HTML-страницей, которая вызывает функцию draw() в момент загрузки. Следующий пример немного прояснит ситуацию. Нарисуем простой прямоугольник – скопируйте следующий код в функцию draw() выше, где написано добавьте здесь все свои фигуры. Он создает экземпляр класса shape и использует функцию strokeRect(). Для рисования фигуры вызывается функция drawShape обертки холста:
 
JavaScript сопровождается HTML-страницей, которая вызывает функцию draw() в момент загрузки. Следующий пример немного прояснит ситуацию. Нарисуем простой прямоугольник – скопируйте следующий код в функцию draw() выше, где написано добавьте здесь все свои фигуры. Он создает экземпляр класса shape и использует функцию strokeRect(). Для рисования фигуры вызывается функция drawShape обертки холста:
  
 +
<source lang=html4strict>
 
  box = new iShape();
 
  box = new iShape();
 
  box.renderShape = function() {
 
  box.renderShape = function() {
Строка 118: Строка 135:
 
   this.c.stroke();
 
   this.c.stroke();
 
  }
 
  }
  ic = new iCanvas(‘canvas’);
+
  ic = new iCanvas('canvas');
 
  // теперь рисуем фигуры
 
  // теперь рисуем фигуры
 
  ic.drawShape(box, {x : 200, y : 100});
 
  ic.drawShape(box, {x : 200, y : 100});
 
  ic.drawShape(box, {x : 100, y : 100});
 
  ic.drawShape(box, {x : 100, y : 100});
 
  ic.drawShape(box, {x : 250, y : 150});
 
  ic.drawShape(box, {x : 250, y : 150});
 +
</source>
 
   
 
   
 
Я вызывал drawShape несколько раз, чтобы показать, как можно повторно использовать фигуру после ее создания. Просто передайте объект и некоторые новые ссылки, и готово – получена новая копия фигуры.
 
Я вызывал drawShape несколько раз, чтобы показать, как можно повторно использовать фигуру после ее создания. Просто передайте объект и некоторые новые ссылки, и готово – получена новая копия фигуры.
Строка 128: Строка 146:
 
Все, что делается с тэгом canvas, можно, по большей части, заложить в объект фигуры и повторно использовать его на холсте. Теперь нарисуем более сложный пример: дачный домик. Это всего лишь набор прямоугольников и треугольников:
 
Все, что делается с тэгом canvas, можно, по большей части, заложить в объект фигуры и повторно использовать его на холсте. Теперь нарисуем более сложный пример: дачный домик. Это всего лишь набор прямоугольников и треугольников:
  
 +
<source lang=html4strict>
 
  house = new iShape();
 
  house = new iShape();
 
  house.renderShape = function() {
 
  house.renderShape = function() {
   this.c.fillStyle = ‘white’;
+
   this.c.fillStyle = 'white';
 
   this.c.fillRect(0, 0, 100, 100); // ставим белый фон
 
   this.c.fillRect(0, 0, 100, 100); // ставим белый фон
 
   this.c.strokeRect(0, 0, 100, 100);
 
   this.c.strokeRect(0, 0, 100, 100);
Строка 136: Строка 155:
 
   this.c.strokeRect(20, 60, 20, 40); // дверь
 
   this.c.strokeRect(20, 60, 20, 40); // дверь
 
   // окна
 
   // окна
   this.c.fillStyle = “rgba(0, 0, 250, 0.3);
+
   this.c.fillStyle = "rgba(0, 0, 250, 0.3)";
 
   this.c.fillRect(60, 60, 20, 20);
 
   this.c.fillRect(60, 60, 20, 20);
 
   this.c.fillRect(20, 20, 20, 20);
 
   this.c.fillRect(20, 20, 20, 20);
 
   this.c.fillRect(60, 20, 20, 20);
 
   this.c.fillRect(60, 20, 20, 20);
 
   this.c.fill();
 
   this.c.fill();
   this.c.fillStyle = “rgba(250, 0, 0, 0.3);
+
   this.c.fillStyle = "rgba(250, 0, 0, 0.3)";
 
   this.c.moveTo(0,0);
 
   this.c.moveTo(0,0);
 
   this.c.lineTo(50,-30);
 
   this.c.lineTo(50,-30);
Строка 148: Строка 167:
 
   this.c.stroke();
 
   this.c.stroke();
 
  }
 
  }
 +
</source>
  
 
fillStyle использует функцию rgba(), позволяющую нам установить прозрачность. Вы можете установить прозрачность «для всего» с помощью .globalAlpha=0.5;. Часть lineTo рисует отрезок на холсте [от текущего положения] до точки (x,y). В данном примере мы используем ее для рисования крыши.
 
fillStyle использует функцию rgba(), позволяющую нам установить прозрачность. Вы можете установить прозрачность «для всего» с помощью .globalAlpha=0.5;. Часть lineTo рисует отрезок на холсте [от текущего положения] до точки (x,y). В данном примере мы используем ее для рисования крыши.
Строка 153: Строка 173:
 
Как и в предыдущем примере, мы можем повторно использовать объекты на холсте с помощью функции drawShape(). Написав
 
Как и в предыдущем примере, мы можем повторно использовать объекты на холсте с помощью функции drawShape(). Написав
  
 +
<source lang=html4strict>
 
  ic.drawShape(house, {x : 200, y : 100});
 
  ic.drawShape(house, {x : 200, y : 100});
 
  ic.drawShape(house, {x : 300, y : 100});
 
  ic.drawShape(house, {x : 300, y : 100});
 
  ic.drawShape(house, {x : 400, y : 100});
 
  ic.drawShape(house, {x : 400, y : 100});
 +
</source>
  
 
вы отстроите целую уличку.
 
вы отстроите целую уличку.
Строка 164: Строка 186:
 
Градиенты можно сделать как линейными, так и радиальными, с помощью функций createLinearGradient и createRadilGradient соответственно. Сам градиент – это набор цветовых переходов, добавляемых с помощью addColorStop. Увидеть – значит понять; вот и посмотрите на градиент от красного до зеленого, затем синего и, наконец, белого.
 
Градиенты можно сделать как линейными, так и радиальными, с помощью функций createLinearGradient и createRadilGradient соответственно. Сам градиент – это набор цветовых переходов, добавляемых с помощью addColorStop. Увидеть – значит понять; вот и посмотрите на градиент от красного до зеленого, затем синего и, наконец, белого.
  
 +
<source lang=html4strict>
 
  gradbox = new iShape();
 
  gradbox = new iShape();
 
  gradbox.renderShape = function() {
 
  gradbox.renderShape = function() {
 
   var gradient = this.c.createLinearGradient(0, 0, 0, 100);
 
   var gradient = this.c.createLinearGradient(0, 0, 0, 100);
 
   // создать градиент – градации от x1,y1 до x2,y2
 
   // создать градиент – градации от x1,y1 до x2,y2
   gradient.addColorStop(0, ‘red’);
+
   gradient.addColorStop(0, 'red');
   gradient.addColorStop(0.25, ‘green’);
+
   gradient.addColorStop(0.25, 'green');
   gradient.addColorStop(0.75, ‘blue’);
+
   gradient.addColorStop(0.75, 'blue');
   gradient.addColorStop(1, ‘white’);
+
   gradient.addColorStop(1, 'white');
 
   this.c.fillStyle = gradient;
 
   this.c.fillStyle = gradient;
 
   this.c.fillRect(0, 0, 100, 100);
 
   this.c.fillRect(0, 0, 100, 100);
 
  }
 
  }
 
  ic.drawShape(gradbox, {x : 300, y : 300});
 
  ic.drawShape(gradbox, {x : 300, y : 300});
 +
</source>
  
 
Функция createLinearGradient задает направление градиента – в нашем случае, вертикальное, от 0,0 до 0,100. Каждая из строк .addColorStop устанавливает цвет в соответствующий точке. Ноль (начало) – это красный, сливающийся с зеленым на четверти пути (0.25), затем синий на трех четвертях и белый в конце (1). Поиграйте с цветами и значениями в addColorStop – например, попытайтесь изменить 0.25 на 0.5, а 0.75 на 0.9.
 
Функция createLinearGradient задает направление градиента – в нашем случае, вертикальное, от 0,0 до 0,100. Каждая из строк .addColorStop устанавливает цвет в соответствующий точке. Ноль (начало) – это красный, сливающийся с зеленым на четверти пути (0.25), затем синий на трех четвертях и белый в конце (1). Поиграйте с цветами и значениями в addColorStop – например, попытайтесь изменить 0.25 на 0.5, а 0.75 на 0.9.
Строка 181: Строка 205:
 
Радиальные градиенты работают так же, как и линейные, используя «цветовые шаги», но создаваемый градиент не вертикальный, а проходящий от центра одного круга до границы другого, следующим образом:
 
Радиальные градиенты работают так же, как и линейные, используя «цветовые шаги», но создаваемый градиент не вертикальный, а проходящий от центра одного круга до границы другого, следующим образом:
  
 +
<source lang=html4strict>
 
  gradblob = new iShape();
 
  gradblob = new iShape();
 
  gradblob.renderShape = function() {
 
  gradblob.renderShape = function() {
 
   var gradient = this.c.createRadialGradient( 50, 50, 1, 50, 50, 100 );
 
   var gradient = this.c.createRadialGradient( 50, 50, 1, 50, 50, 100 );
   gradient.addColorStop(0, ‘rgba(0,0,228,1));
+
   gradient.addColorStop(0, 'rgba(0,0,228,1)');
   gradient.addColorStop(1, ‘rgba(228,0,0,0.5));
+
   gradient.addColorStop(1, 'rgba(228,0,0,0.5)');
 
   this.c.fillStyle = gradient;
 
   this.c.fillStyle = gradient;
 
   this.c.fillRect(0, 0, 100, 100);
 
   this.c.fillRect(0, 0, 100, 100);
 
  }
 
  }
 
  ic.drawShape(gradblob, {x : 200, y : 200});
 
  ic.drawShape(gradblob, {x : 200, y : 200});
 +
</source>
  
 
Синтаксис примерно тот же, так что можете сразу начинать эксперименты с радиальными градиентами. Их можно даже создавать между двумя не выровненными окружностями – попробуйте установить this.c.createRadialGradient(50, 50, 1, 5, 5, 100).
 
Синтаксис примерно тот же, так что можете сразу начинать эксперименты с радиальными градиентами. Их можно даже создавать между двумя не выровненными окружностями – попробуйте установить this.c.createRadialGradient(50, 50, 1, 5, 5, 100).
Строка 195: Строка 221:
 
Теперь займемся кривыми. Тэг canvas предлагает несколько способов создания кривых, одна из которых – дуга, позволяющая рисовать круги на холсте:
 
Теперь займемся кривыми. Тэг canvas предлагает несколько способов создания кривых, одна из которых – дуга, позволяющая рисовать круги на холсте:
  
 +
<source lang=html4strict>
 
  this.c.arc(50, 50, 20, 0, 2*Math.PI, false);
 
  this.c.arc(50, 50, 20, 0, 2*Math.PI, false);
 +
</source>
  
 
Первые и второй аргументы определяют центр дуги, а третий – радиус. Четвертый аргумент – угол начала дуги, пятый – конца. Шестой определяет, рисовать круг по часовой стрелке или против.
 
Первые и второй аргументы определяют центр дуги, а третий – радиус. Четвертый аргумент – угол начала дуги, пятый – конца. Шестой определяет, рисовать круг по часовой стрелке или против.
Строка 203: Строка 231:
 
Аргументами для quadraticCurveTo являются координаты x и y исходной точки и точки назначения. Точка назначения определяет, куда вести кривую. Если вы незнакомы с таким типом кривых, посмотрите, как меняется кривая при изменении первых двух аргументов. Добавив градиент к дугам, нарисуем пару-тройку вытаращенных глаз:
 
Аргументами для quadraticCurveTo являются координаты x и y исходной точки и точки назначения. Точка назначения определяет, куда вести кривую. Если вы незнакомы с таким типом кривых, посмотрите, как меняется кривая при изменении первых двух аргументов. Добавив градиент к дугам, нарисуем пару-тройку вытаращенных глаз:
  
 +
<source lang=html4strict>
 
  eye = new iShape();
 
  eye = new iShape();
 
  eye.renderShape = function() {
 
  eye.renderShape = function() {
Строка 219: Строка 248:
 
   this.c.globalAlpha = 0.5;
 
   this.c.globalAlpha = 0.5;
 
   var g = this.c.createRadialGradient( 50, 50, 1, 50, 50, 20 );
 
   var g = this.c.createRadialGradient( 50, 50, 1, 50, 50, 20 );
   g.addColorStop(0, ‘rgba(0,0,228,1));
+
   g.addColorStop(0, 'rgba(0,0,228,1)');
   g.addColorStop(1, ‘rgba(228,199,0,0));
+
   g.addColorStop(1, 'rgba(228,199,0,0)');
   //this.c.fillStyle = ‘green’;
+
   //this.c.fillStyle = 'green';
 
   this.c.fillStyle = g;
 
   this.c.fillStyle = g;
 
   this.c.fill();
 
   this.c.fill();
Строка 230: Строка 259:
 
  ic.drawShape(eye, {x : 100, y : 100});
 
  ic.drawShape(eye, {x : 100, y : 100});
 
  ic.drawShape(eye, {x : 300, y : 100});
 
  ic.drawShape(eye, {x : 300, y : 100});
 +
</source>
  
 
Вы, наверное, заметили, что мы ввели новые функции для глаза: beginPath() и closePath(). Path, контур – это то, что функции fill() и
 
Вы, наверное, заметили, что мы ввели новые функции для глаза: beginPath() и closePath(). Path, контур – это то, что функции fill() и
Строка 239: Строка 269:
 
Изображения можно помещать на холст с помощью drawImage(). Эта функция принимает в качестве аргументов объект изображения JavaScript и x / y координаты, но если вы хотите избежать возникновения исключения «не доступно», изображение должно быть загружено до drawImage(). Далее, как и с линиями и заполнением, вы можете повернуть холст до того, как нарисуете изображение, используя функцию .rotate():
 
Изображения можно помещать на холст с помощью drawImage(). Эта функция принимает в качестве аргументов объект изображения JavaScript и x / y координаты, но если вы хотите избежать возникновения исключения «не доступно», изображение должно быть загружено до drawImage(). Далее, как и с линиями и заполнением, вы можете повернуть холст до того, как нарисуете изображение, используя функцию .rotate():
  
 +
<source lang=html4strict>
 
  this.c.rotate(-45 * Math.PI / 180);
 
  this.c.rotate(-45 * Math.PI / 180);
 +
</source>
  
 
=== Взаимодействие с холстом ===
 
=== Взаимодействие с холстом ===
Строка 247: Строка 279:
 
Цель этого примера – рисовать фигуру на холсте по щелчку мыши с помощью drawShape(). На текущий момент единственной фигурой является домик. Мы будем слушать любые щелчки мыши для id canvas.
 
Цель этого примера – рисовать фигуру на холсте по щелчку мыши с помощью drawShape(). На текущий момент единственной фигурой является домик. Мы будем слушать любые щелчки мыши для id canvas.
  
 +
<source lang=html4strict>
 
  function setupInteraction() {
 
  function setupInteraction() {
 
   currentDrawShape = house;
 
   currentDrawShape = house;
 
   new Event.observe(
 
   new Event.observe(
   ‘canvas’,
+
   'canvas',
   ‘click’,
+
   'click',
 
   function(evt) {
 
   function(evt) {
 
     ic.drawShape(currentDrawShape, {x : Event.pointerX(evt), y :
 
     ic.drawShape(currentDrawShape, {x : Event.pointerX(evt), y :
Строка 258: Строка 291:
 
   );
 
   );
 
  }
 
  }
 +
</source>
  
 
Вызов setupInteraction() в конце функции draw() позволяет помещать объекты на холст с помощью мыши. Пока от него мало проку, разве что вы затеяли нарисовать много-много домиков. Куда полезнее будет позволить пользователю самому выбирать из доступных фигур-
 
Вызов setupInteraction() в конце функции draw() позволяет помещать объекты на холст с помощью мыши. Пока от него мало проку, разве что вы затеяли нарисовать много-много домиков. Куда полезнее будет позволить пользователю самому выбирать из доступных фигур-
 
шаблонов – как в Dia или OmniGraffe. На уровне кода, для того, чтобы изменить изображение, которое мы рисуем, достаточно изменить значение currentDrawImage. Поэтому, если вы добавите строку
 
шаблонов – как в Dia или OmniGraffe. На уровне кода, для того, чтобы изменить изображение, которое мы рисуем, достаточно изменить значение currentDrawImage. Поэтому, если вы добавите строку
  
 +
<source lang=html4strict>
 
  currentDrawShape = eye;
 
  currentDrawShape = eye;
 +
</source>
  
 
в setupInteraction(), то будете рисовать глаза вместо домов.
 
в setupInteraction(), то будете рисовать глаза вместо домов.
Строка 268: Строка 304:
 
Включим функцию addOption() для добавления фигуры в перечень шаблонов:
 
Включим функцию addOption() для добавления фигуры в перечень шаблонов:
  
 +
<source lang=html4strict>
 
  function addShapeOption(shape, name) {
 
  function addShapeOption(shape, name) {
 
  shapeOptions[name] = shape;
 
  shapeOptions[name] = shape;
  $(‘slist’).innerHTML += <li><a href=#onClick=”currentDrawSh
+
  $('slist').innerHTML += '<nowiki><li></nowiki><a href="#" onClick="currentDrawSh
  ape = shapeOptions[\’’+name+\]; return false;>+name+</li>;
+
  ape = shapeOptions[\''+name+'\']; return false;">'+name+'</li>';
 
  }
 
  }
 +
</source>
  
 
Это хороший способ для пользователя выбрать текущий шаблон currentDrawShape, и все, что нам надо сделать для активации опции, это вызвать функцию
 
Это хороший способ для пользователя выбрать текущий шаблон currentDrawShape, и все, что нам надо сделать для активации опции, это вызвать функцию
  
  addShapeOption(theShapeObject, ‘name of object’);
+
<source lang=html4strict>
 +
  addShapeOption(theShapeObject, 'name of object');
 +
</source>
  
 
В заключительной версии исходного кода на сайте каждая фигура сама добавляет себя в список, а пользователь может из него выбирать.
 
В заключительной версии исходного кода на сайте каждая фигура сама добавляет себя в список, а пользователь может из него выбирать.
Строка 296: Строка 336:
 
*clearRect(x2,y1,x2,y2) очищает кусок холста.
 
*clearRect(x2,y1,x2,y2) очищает кусок холста.
 
*clip() создает путь отсечения так, что вы можете ограничить холст определенной фигурой, например, кругом или сложным многоугольником.
 
*clip() создает путь отсечения так, что вы можете ограничить холст определенной фигурой, например, кругом или сложным многоугольником.
 +
 +
[[Категория:Hardcore Linux]]
 +
[[Категория:Дэн Фрост]]

Текущая версия на 12:29, 7 января 2009

Содержание

[править] Canvas: Холст для web-картин

Новый HTML-элемент canvas позволяет программировать графику в браузере. Дэн Фрост покажет вам, как использовать этот мощный тэг.

Рисование графики на web-страницах обычно отдавалось на откуп Flash или библиотекам на стороне сервера. Больше такому не бывать! Забудьте зависть и удовлетворите свои амбиции стать художником – по крайней мере в Firefox и Opera, благодаря тэгу <canvas>.

Впервые введенный фирмой Apple, этот тэг предоставляет программисту холст, чтобы рисовать на нем прямые, дуги, квадраты и так далее. Комбинируя простые элементы, можно получать блестящие результаты – как, вы узнаете чуть позже. В настоящий момент тэг canvas поддерживается браузерами Firefox, Opera, Safari и включен в спецификацию HTML 5 группой WHATWG (Web Hypertext Application Technology Working Group – Рабочая группа по технологии гипертекстовых приложений), что вселяет надежду на его грядущую повсеместную реализацию. В качестве краткого введения, на данном уроке я собираюсь показать, как создать простые фигуры с помощью тэга canvas, поместить эти фигуры в объектную модель и, наконец, сделать их интерактивными.

[править] Привет, мир графики

Чтобы работать с тэгом canvas, достаточно создать небольшой HTML-файл и открыть его в Firefox. Для начала рассмотрим несложный пример. Тэг canvas добавляется на страницу обычным образом. Атрибуты «ширина» и «высота» говорят сами за себя; но вы также можете включить стандартные HTML-атрибуты, типа class, id, style и других:

 <canvas id="canvas" style="border : thin solid black;"
 width="600" height="400"></canvas>

Добавьте этот кусок кода на пустую HTML-страницу и откройте ее в браузере с помощью File > Open (Файл > Открыть). Все, что вы видите – это пустая область с границей, так как наш код устанавливает тэг canvas, но ничего другого не делает: просто обозначает пустой холст на странице.

Самое интересное начинается в JavaScript. JS используется для рисования линий, кривых, областей и изображений на объекте canvas. Чтобы создать две линии, добавьте это в только что созданный файл и обновите страницу в Firefox:

 <canvas id="canvas" width="600" height="400" style="background-color : #c0c0c0;"></canvas>
 <script>
 var canvas = document.getElementById('canvas');
 ctx = canvas.getContext('2d');
 ctx.lineTo(100, 100);
 ctx.lineTo(200, 300);
 ctx.lineTo(300, 100);
 ctx.lineTo(300, 300);
 ctx.stroke();
 </script>

Давайте разберемся. Сначала мы получаем HTML-элемент (или узел) по id 'canvas'. Затем из элемента canvas мы получаем контекстный объект, его мы рассмотрим далее. Объект используется для собственно рисования.

Вот и все, что необходимо для применения тэга canvas: HTML-тэг и немного JavaScript. Вся ваша разработка может вестись с помощью Firefox и тестироваться вне сервера, так как логика и рисование находится на стороне клиента. Но что мы можем? Чтобы подстрекнуть свое воображение, гляньте на панель Dashboard от Apple (сайт http://www.apple.com/macosx/features/dashboard). Кроме того, всплывает масса мелких примеров: от реализации Paint (CanvasPaint) до восхитительных игр вроде Blobsallad (http://blobsallad.se) и Canvascope (http://www.abrahamjoffe.com.au/ben/canvascape).

Программировать с помощью canvas совсем не сложно – мой пример это доказывает. Но если вам неохота писать тысячи строк лапшеобразного кода для отрисовки сложного извива ДНК, поможет объектная модель. Наша следующая задача – поместить функциональность canvas в очень простой (50 строк) модуль, для упрощения управления фигурами.

[править] Расширяем холст

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

Фактически мы рисуем не на объекте canvas, а на его 2D-контексте. Чтобы получить контекст canvas, мы используем функцию getContext. Контекст – это объект, содержащий функции для рисования линий, блоков цветных изображений и так далее. Линии он называет штрихами [stroke], а цветовые блоки – заливкой [fill]:

 var canvas = document.getElementById('canvas');
 ctx = canvas.getContext("2d");
 ctx.strokeRect(50, 50, 100, 100);
 ctx.stroke();
 ctx.fillRect(100, 100, 100, 100);
 ctx.fill();

Большая часть функций понятна: strokeRect() рисует прямоугольник, fillRect() его заполняет. У strokeRect() и fillRect() похожие параметры – x и y верхнего левого угла прямоугольника, а также ширина и высота. Canvas ведет отсчет от левого верхнего угла, то есть 50, 50 означает 50 пикселей слева и 50 пикселей сверху. Тем, кто привык мыслить в терминах координат обычных графиков, тут легко и запутаться! Заметим также, что нам надо вызывать функции .stroke() и .fill(). Не забудьте это сделать, иначе останетесь с пустым холстом.

Цвет линий и заливки устанавливается на холсте с помощью lineStyle и fillStyle, которые принимают похожие аргументы. Самый простой способ ввести цвет – указать его: ctx.lineStyle='red' – или обозначить шестнадцатеричным числом: ctx.lineStyle='#a0b0c0';. Для получения прозрачности предусмотрен альфа-канал в функции rgba(), принимающей четыре параметра: красный, зеленый, синий и альфа. Например:

 ctx.lineStyle = 'red';
 ctx.fillStyle = 'rgb(200, 100, 0, 0.5)';

Когда две линии пересекаются, стиль их сочленения можно выбрать с помощью lineJoin:

 ctx.lineJoin = 'curve';

Две последние важные концепции – перенос и поворот. Перенос – это передвижение в другую точку холста; поворот, очевидно, и есть поворот холста. Оба действия производятся до того, как вы начнете рисовать. Например, если вы хотите нарисовать прямоугольник под 45°, то сначала вам надо добавить функцию поворота:

 ctx.rotate(-45 * Math.PI / 180);
 ctx.strokeRect(50, 50, 100, 100);
 ctx.stroke();

Все эти настройки внутри холста, то есть lineStyle, fillStyle, поворот и перенос, можно сохранять в стеке состояний, а потом брать их оттуда. Это позволяет понаделать кучу стилевых настроек, а затем разом их отменить, закончив рисование:

 ctx.save(); // Сохранить текущее состояние, чтобы мы могли его восстановить
 ctx.rotate(-45 * Math.PI / 180);
 ctx.lineStyle = 'blue';
 ctx.strokeRect(50, 50, 100, 100); // нарисовать прямоугольник
 ctx.stroke();
 ctx.restore(); // восстановить lineStyle и поворот

Функции save и restore очень полезны, если вы производите много переносов и поворотов: они избавят вас от необходимости держать в памяти всю серию изменений.

[править] Рисование фигур

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

 function draw() {
  ic = new iCanvas('canvas');
  // добавьте здесь все свои фигуры
  setupInteraction();
 }
 function setupInteraction() { /* мы рассмотрим ее позднее*/ }
  iCanvas = {};
  iCanvas = Class.create();
  iCanvas.prototype = {
     /* ..вырезано.. */
  }
  // используется для инкапсуляции фигур
  iShape = {};
  iShape = Class.create();
  iShape.prototype = {
    /* ..вырезано... */
 }
  // рисование будет проходить в этой функции
  renderShape : function() { },
 }

Полная версия исходного кода доступна по адресу http://www.linuxformat.co.uk/mag/canvas.htm, но единственной важной частью является renderShape(), так как все, что мы будем туда писать, будет родным кодом холста – другие функции (например, drawShape()) добавляются для упрощения кодирования.

JavaScript сопровождается HTML-страницей, которая вызывает функцию draw() в момент загрузки. Следующий пример немного прояснит ситуацию. Нарисуем простой прямоугольник – скопируйте следующий код в функцию draw() выше, где написано добавьте здесь все свои фигуры. Он создает экземпляр класса shape и использует функцию strokeRect(). Для рисования фигуры вызывается функция drawShape обертки холста:

 box = new iShape();
 box.renderShape = function() {
  // this.c это контекст холста
  this.c.fillRect(0, 0, 100, 100);
  this.c.strokeRect(0, 0, 100, 100);
  this.c.stroke();
 }
 ic = new iCanvas('canvas');
 // теперь рисуем фигуры
 ic.drawShape(box, {x : 200, y : 100});
 ic.drawShape(box, {x : 100, y : 100});
 ic.drawShape(box, {x : 250, y : 150});

Я вызывал drawShape несколько раз, чтобы показать, как можно повторно использовать фигуру после ее создания. Просто передайте объект и некоторые новые ссылки, и готово – получена новая копия фигуры.

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

 house = new iShape();
 house.renderShape = function() {
  this.c.fillStyle = 'white';
  this.c.fillRect(0, 0, 100, 100); // ставим белый фон
  this.c.strokeRect(0, 0, 100, 100);
  this.c.fill();
  this.c.strokeRect(20, 60, 20, 40); // дверь
  // окна
  this.c.fillStyle = "rgba(0, 0, 250, 0.3)";
  this.c.fillRect(60, 60, 20, 20);
  this.c.fillRect(20, 20, 20, 20);
  this.c.fillRect(60, 20, 20, 20);
  this.c.fill();
  this.c.fillStyle = "rgba(250, 0, 0, 0.3)";
  this.c.moveTo(0,0);
  this.c.lineTo(50,-30);
  this.c.lineTo(100,0);
  this.c.fill();
  this.c.stroke();
 }

fillStyle использует функцию rgba(), позволяющую нам установить прозрачность. Вы можете установить прозрачность «для всего» с помощью .globalAlpha=0.5;. Часть lineTo рисует отрезок на холсте [от текущего положения] до точки (x,y). В данном примере мы используем ее для рисования крыши.

Как и в предыдущем примере, мы можем повторно использовать объекты на холсте с помощью функции drawShape(). Написав

 ic.drawShape(house, {x : 200, y : 100});
 ic.drawShape(house, {x : 300, y : 100});
 ic.drawShape(house, {x : 400, y : 100});

вы отстроите целую уличку.

[править] Градиенты, дуги и кривые

Градиенты используются как стили заливки – создайте объект градиента и используйте его для установки fillStyle перед рисованием. Градиенты можно сделать как линейными, так и радиальными, с помощью функций createLinearGradient и createRadilGradient соответственно. Сам градиент – это набор цветовых переходов, добавляемых с помощью addColorStop. Увидеть – значит понять; вот и посмотрите на градиент от красного до зеленого, затем синего и, наконец, белого.

 gradbox = new iShape();
 gradbox.renderShape = function() {
  var gradient = this.c.createLinearGradient(0, 0, 0, 100);
  // создать градиент – градации от x1,y1 до x2,y2
  gradient.addColorStop(0, 'red');
  gradient.addColorStop(0.25, 'green');
  gradient.addColorStop(0.75, 'blue');
  gradient.addColorStop(1, 'white');
  this.c.fillStyle = gradient;
  this.c.fillRect(0, 0, 100, 100);
 }
 ic.drawShape(gradbox, {x : 300, y : 300});

Функция createLinearGradient задает направление градиента – в нашем случае, вертикальное, от 0,0 до 0,100. Каждая из строк .addColorStop устанавливает цвет в соответствующий точке. Ноль (начало) – это красный, сливающийся с зеленым на четверти пути (0.25), затем синий на трех четвертях и белый в конце (1). Поиграйте с цветами и значениями в addColorStop – например, попытайтесь изменить 0.25 на 0.5, а 0.75 на 0.9.

Радиальные градиенты работают так же, как и линейные, используя «цветовые шаги», но создаваемый градиент не вертикальный, а проходящий от центра одного круга до границы другого, следующим образом:

 gradblob = new iShape();
 gradblob.renderShape = function() {
  var gradient = this.c.createRadialGradient( 50, 50, 1, 50, 50, 100 );
  gradient.addColorStop(0, 'rgba(0,0,228,1)');
  gradient.addColorStop(1, 'rgba(228,0,0,0.5)');
  this.c.fillStyle = gradient;
  this.c.fillRect(0, 0, 100, 100);
 }
 ic.drawShape(gradblob, {x : 200, y : 200});

Синтаксис примерно тот же, так что можете сразу начинать эксперименты с радиальными градиентами. Их можно даже создавать между двумя не выровненными окружностями – попробуйте установить this.c.createRadialGradient(50, 50, 1, 5, 5, 100).

Теперь займемся кривыми. Тэг canvas предлагает несколько способов создания кривых, одна из которых – дуга, позволяющая рисовать круги на холсте:

 this.c.arc(50, 50, 20, 0, 2*Math.PI, false);

Первые и второй аргументы определяют центр дуги, а третий – радиус. Четвертый аргумент – угол начала дуги, пятый – конца. Шестой определяет, рисовать круг по часовой стрелке или против.

Чудненько. Но для рисования более сложных кривых лучше использовать quadraticCurveTo или bezierCurveTo. В следующем примере мы возьмем quadraticCurveTo и нарисуем глаз, точнее, верхнее и нижнее веки. Потом, чтобы правильно выровнять глаза, повернем холст на 45° с помощью функции rotate().

Аргументами для quadraticCurveTo являются координаты x и y исходной точки и точки назначения. Точка назначения определяет, куда вести кривую. Если вы незнакомы с таким типом кривых, посмотрите, как меняется кривая при изменении первых двух аргументов. Добавив градиент к дугам, нарисуем пару-тройку вытаращенных глаз:

 eye = new iShape();
 eye.renderShape = function() {
  this.c.save();
  this.c.beginPath();
  this.c.translate(0, 35);
  this.c.rotate(-45 * Math.PI / 180);
  this.c.moveTo(0, 0);
  this.c.quadraticCurveTo(100, 0, 100, 100);
  this.c.quadraticCurveTo(0, 100, 0, 0);
  this.c.stroke();
  this.c.closePath();
  this.c.beginPath();
  // рисуем дугу
  this.c.arc(50, 50, 20, 0, 2*Math.PI, false);
  this.c.globalAlpha = 0.5;
  var g = this.c.createRadialGradient( 50, 50, 1, 50, 50, 20 );
  g.addColorStop(0, 'rgba(0,0,228,1)');
  g.addColorStop(1, 'rgba(228,199,0,0)');
  //this.c.fillStyle = 'green';
  this.c.fillStyle = g;
  this.c.fill();
  this.c.stroke();
  this.c.closePath();
  this.c.restore();
 }
 ic.drawShape(eye, {x : 100, y : 100});
 ic.drawShape(eye, {x : 300, y : 100});

Вы, наверное, заметили, что мы ввели новые функции для глаза: beginPath() и closePath(). Path, контур – это то, что функции fill() и stroke() заполняют и вдоль чего рисуют. На глазе мы закрыли один путь и начали другой, так как круги посреди глаза не соединяются с кривыми второго порядка, образующими верхнюю и нижнюю часть глаза. Закомментируйте this.c.closePath() и this.c.beginPath() посреди глаза и посмотрите на результат: у вас получится линия от левой стороны глаза до центра окружности. Так как путь между концом кривой и началом дуги не закрыт, метод stroke соединяет их.

Изображения можно помещать на холст с помощью drawImage(). Эта функция принимает в качестве аргументов объект изображения JavaScript и x / y координаты, но если вы хотите избежать возникновения исключения «не доступно», изображение должно быть загружено до drawImage(). Далее, как и с линиями и заполнением, вы можете повернуть холст до того, как нарисуете изображение, используя функцию .rotate():

 this.c.rotate(-45 * Math.PI / 180);

[править] Взаимодействие с холстом

Картинки рисовать мы научились. Но ключевая задача сети – обеспечить взаимодействие пользователя с объектами. На нашем уроке мы реализовали возможность создания копий фигур всего одной строкой кода, drawShape(). Форма каждого объекта может быть сколь угодно сложной – изображение, кривые, штрихи и так далее. Чтобы превратить этот скромный базис в простую программу рисования, надо просто следить за событиями мыши. Я собираюсь использовать Script. aculo.us, коллекцию интерфейсных библиотек JavaScript, для отслеживания событий мыши, а код поставляется вместе с примером на сайте. Вы можете использовать любую подобную среду.

Цель этого примера – рисовать фигуру на холсте по щелчку мыши с помощью drawShape(). На текущий момент единственной фигурой является домик. Мы будем слушать любые щелчки мыши для id canvas.

 function setupInteraction() {
  currentDrawShape = house;
  new Event.observe(
   'canvas',
   'click',
   function(evt) {
    ic.drawShape(currentDrawShape, {x : Event.pointerX(evt), y :
    Event.pointerY(evt)});
   }
  );
 }

Вызов setupInteraction() в конце функции draw() позволяет помещать объекты на холст с помощью мыши. Пока от него мало проку, разве что вы затеяли нарисовать много-много домиков. Куда полезнее будет позволить пользователю самому выбирать из доступных фигур- шаблонов – как в Dia или OmniGraffe. На уровне кода, для того, чтобы изменить изображение, которое мы рисуем, достаточно изменить значение currentDrawImage. Поэтому, если вы добавите строку

 currentDrawShape = eye;

в setupInteraction(), то будете рисовать глаза вместо домов.

Включим функцию addOption() для добавления фигуры в перечень шаблонов:

 function addShapeOption(shape, name) {
 shapeOptions[name] = shape;
 $('slist').innerHTML += '<nowiki><li></nowiki><a href="#" onClick="currentDrawSh
 ape = shapeOptions[\''+name+'\']; return false;">'+name+'</li>';
 }

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

 addShapeOption(theShapeObject, 'name of object');

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

Тэг canvas будет одним из самых интересных добавлений в спецификацию HTML, c учетом огромного интереса к нему в так называемых средах Web 2.0. Ныне доступные примеры покамест довольно примитивны, но потенциал огромен. Уже создаются библиотеки для графиков, и комбинируя их с простыми действиями, можно добиться поразительных – и полезных приложений.


[править] Ресурсы

[править] Попробуйте еще

Не останавливайтесь на достигнутом – попробуйте другие функции...

  • createPattern() превращает изображения или другие объекты canvas в шаблоны для многократного использования.
  • clearRect(x2,y1,x2,y2) очищает кусок холста.
  • clip() создает путь отсечения так, что вы можете ограничить холст определенной фигурой, например, кругом или сложным многоугольником.
Персональные инструменты
купить
подписаться
Яндекс.Метрика