Контекст canvas: градиенты, узоры, тени, стили линий

5 (100%) 5 vote[s]

В предыдущих примерах мы видели, как установить текущий цвет, используя свойства strokeStyle и fillStyle объекта контекста canvas. Рассмотрим другие свойства, которые можно передавать в контекст canvas: как мы можем использовать градиенты или узоры/ текстуры/изображения (другими словами: заливка фигур или заполнение контуров фигур некоторыми повторяющимися изображениями), рисование теней, рассмотрим многократный загрузчик изображений, способы стилизации линий.

Цвета и прозрачность

В canvas можно использовать тот же синтаксис для цветов, который поддерживается CSS3 . Следующие строки показывают возможные значения/синтаксис:

ctx.strokeStyle = 'red';
ctx.fillStyle = "#00ff00";
ctx.strokeStyle = "rgb(0, 0, 255)";
ctx.fillStyle = "rgba(0, 0, 255, 0.5)";

Обратите внимание, что:

      • Все значения являются строками.
      • Строка 4 определяет «прозрачность цвета», «rgba» означает «альфа-канал». Его значение находится в диапазоне от 0 до 1, где 0 означает «полностью прозрачный», а 1 означает «непрозрачный».

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

Исходный код:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas {
         border: 1px solid black;
     }
 </style>
 <script>
   var canvas, ctx;
 
   function init() {
     // Эта функция вызывается после загрузки страницы
      // 1 - Получить холст
     canvas = document.getElementById('myCanvas');
     // 2 - Получить context
     ctx=canvas.getContext('2d');
     // 3 - можем рисовать
     drawSomething();
   }
   function drawSomething() {
     // установить глобальные значения контекста
     ctx.fillStyle='rgba(0, 0, 255, 0.2)';
     ctx.fillRect(150, 20, 200, 100);
     ctx.fillRect(100, 50, 200, 100);
     
     ctx.fillStyle = "blue";
     ctx.fillRect(50, 100, 200, 100);
   }
 </script>
 </head>
<body onload="init();">
    <canvas id="myCanvas" width="400" height="220">
            Your browser does not support the canvas tag.
    </canvas>
</body>
</html>

Контекст canvas: линейные градиенты

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

Исходный код:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas1 {
         border: 1px solid black;
     }
 </style>
  <script>
    var canvas, ctx, grd;

function init() {
  // Хорошая практика 1: установите глобальные переменные canvas, ctx, градиенты и т. l. здесь
   canvas = document.querySelector('#myCanvas1');
   ctx = canvas.getContext('2d');
  
   // Градиент, который мы создаем, также является глобальной переменной, мы
    // сможем использовать его для рисования разных фигур
    // в разных функциях
  grd = ctx.createLinearGradient(0, 0, 300, 0);
              
  // Попробуйте добавить цвета с первым параметром от 0 до 1
  grd.addColorStop(0, "blue"); 
  grd.addColorStop(0.5, "white");
  grd.addColorStop(1, "red"); 

  draw();
}

function draw() {
  ctx.fillStyle = grd;
  ctx.fillRect(0, 0, 300, 200);
}

  </script>
 </head>
<body onload="init();">   
   <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas>
</body>
</html>

Концепция линейного градиента рассматривается как «невидимый» прямоугольник, в котором набор цветов интерполируется вдоль воображаемой линии.

Градиент становится видимым, когда мы рисуем фигуры поверх невидимого градиента, и когда свойство fillStyle или strokeStyle имеет значение этого градиента.

Вот основные необходимые шаги рисования градиента:

Шаг 1: определить линейный градиент

Синтаксис: 

ctx . createLinearGradient ( x0 , y0 , x1 , y1 );

… где параметры (x0, y0) и (x1, y1) определяют «направление градиента» (как вектор с начальной и конечной точкой). Это направление — невидимая линия, вдоль которой будут интерполироваться цвета, из которых состоит градиент.

Давайте посмотрим на пример:

grd = ctx . createLinearGradient ( 0 , 0 , 300 , 0 );

Эта линия определяет направление градиента: виртуальную невидимую линию, которая проходит от верхнего левого угла холста (0, 0) до верхнего правого угла холста (300, 0). Интерполированные цвета будут распространяться вдоль этой линии. 

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

Шаг 2: добавьте количество «цветных остановок» к этому градиенту

Мы добавим набор «цветов» и «остановок» к этому градиенту. Остановки идут от 0 (начало виртуальной линии, определенной выше) до 1 (конец виртуальной линии). Цвет, связанный со значением 0,5, будет прямо в середине виртуальной линии.

Вот пример перехода от синего к белому, затем к красному, с пропорциональными интервалами. Мы определяем три цвета: синий в позиции 0, белый в позиции 0.5 и красный в позиции 1:

grd.addColorStop(0, "blue");
grd.addColorStop(0.5, "white");
grd.addColorStop(1, "red");

Шаг 3: нарисуйте фигуру

Сначала давайте установим fillStyle или strokeStyle контекста с этим градиентом, затем давайте нарисуем некоторые фигуры «поверх градиента».

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

ctx . fillStyle = grd;
ctx . fillRect ( 0 , 0 , 300 , 200 );

Изменение направления градиента

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

grd = ctx . createLinearGradient ( 0 , 0 , 300 , 200 );

…, тогда вы определите градиент, который идет от верхнего левого угла холста до нижнего правого холста:

Рисование фигур, которые не покрывают весь градиент

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

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

Нарисовать шахматную доску можно таким способом:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas1 {
         border: 1px solid black;
     }
 </style>
  <script>
   var canvas, ctx, grd;

function init() {
   canvas = document.querySelector('#myCanvas1');
   ctx = canvas.getContext('2d');

  grd = ctx.createLinearGradient(0, 0, 300, 200);
              
  grd.addColorStop(0, "blue"); 
  grd.addColorStop(0.5, "white");
  grd.addColorStop(1, "red"); 

  draw();
}
//код, который рисует шахматную доску
function draw() {
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 50, 50);
ctx.fillRect(100, 0, 50, 50);
ctx.fillRect(200, 0, 50, 50);
ctx.fillRect(50, 50, 50, 50);
ctx.fillRect(150, 50, 50, 50);
ctx.fillRect(250, 50, 50, 50);
ctx.fillRect(0, 100, 50, 50);
ctx.fillRect(100, 100, 50, 50);
ctx.fillRect(200, 100, 50, 50);
ctx.fillRect(50, 150, 50, 50);
ctx.fillRect(150, 150, 50, 50);
ctx.fillRect(250, 150, 50, 50);
}
 
  </script>
 </head>
<body onload="init();">   
   <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas>
</body>
</html>

Код, который рисует шахматную доску, довольно уродлив. Было бы лучше использовать цикл:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas {
         border: 1px solid black;
     }
 </style>
  <script>
   var canvas, ctx, grd;

function init() {
   canvas = document.querySelector('#myCanvas');
   ctx = canvas.getContext('2d');
  
  grd = ctx.createLinearGradient(0, 0, 300, 300);
              
  grd.addColorStop(0, "blue"); 
  grd.addColorStop(0.5, "white");
  grd.addColorStop(1, "red"); 

  drawCheckboard(8);
}

function drawCheckboard(n) {
  ctx.fillStyle = grd;
  ctx.lineWidth=10;
  
  var l = canvas.width;
  var h = canvas.height;

  var cellWidth = l / n;
  var cellHeight = h / n;
  
  for(i = 0; i < n; i++) {
    for(j = i % 2; j < n; j+=2) {
      ctx.fillRect(cellWidth*i, cellHeight*j, cellWidth, cellHeight); 
    }  
  }
} 
  </script>
 </head>
<body onload="init();">   
   <canvas id="myCanvas" width="300" height=300>Your browser does not support the canvas tag.</canvas>
</body>
</html>

Рисование контурных фигур с градиентами

Так же, как мы использовали fillStyle и fillRect для рисования прямоугольников, заполненных градиентом, мы также можем использовать контекст canvas strokeStyle и strokeRect для рисования каркасных прямоугольников. В следующем примере, который является просто изменением предыдущего, мы использовали свойство lineWidth, чтобы установить контур прямоугольников на 5 пикселей:

Исходный код:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas {
         border: 1px solid black;
     }
 </style>
  <script>
    var canvas, ctx, grd;

function init() {
   canvas = document.querySelector('#myCanvas');
   ctx = canvas.getContext('2d');
  
  grd = ctx.createLinearGradient(0, 0, 300, 300);
              
  grd.addColorStop(0, "blue"); 
  grd.addColorStop(0.5, "white");
  grd.addColorStop(1, "red"); 

  drawCheckboard(8);
}

function drawCheckboard(n) {
  ctx.strokeStyle = grd;
  ctx.lineWidth=10;
  
  var l = canvas.width;
  var h = canvas.height;

  var cellWidth = l / n;
  var cellHeight = h / n;
  
  for(i = 0; i < n; i++) {
    for(j = i % 2; j < n; j+=2) {
      ctx.strokeRect(cellWidth*(i), cellHeight*j, cellWidth, cellHeight); 
    }  
  }
}
  </script>
 </head>
<body onload="init();">   
   <canvas id="myCanvas" width="300" height=300>Your browser does not support the canvas tag.</canvas>
</body>
</html>

Рисование фигур с одинаковым градиентом в целом

Как нарисовать шахматную доску с градиентом в каждой ячейке? Как мы можем сделать это с одним единственным градиентом без воссоздания его для каждой клетки?

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

Исходный код:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas {
         border: 1px solid black;
     }
 </style>
  <script>
    var canvas, ctx, grd;

function init() {
   canvas = document.querySelector('#myCanvas');
   ctx = canvas.getContext('2d');

  drawCheckboard(8);
}

function setGradient(x, y, width, height) {
    grd = ctx.createLinearGradient(x, y, width, height);
              
  grd.addColorStop(0, "blue"); 
  grd.addColorStop(0.5, "white");
  grd.addColorStop(1, "red"); 
  ctx.fillStyle = grd;

}

function drawCheckboard(n) {
  
  var l = canvas.width;
  var h = canvas.height;

  var cellWidth = l / n;
  var cellHeight = h / n;
  
  for(i = 0; i < n; i+=2) {
    for(j = 0; j < n; j++) {
      var x = cellWidth*(i+j%2);
      var y = cellHeight*j;
      setGradient(x, y, x+cellWidth, y+cellHeight);
      ctx.fillRect(x, y, cellWidth, cellHeight); 
    }  
  }
}
  </script>
 </head>
<body onload="init();">   
   <canvas id="myCanvas" width="300" height=300>Your browser does not support the canvas tag.</canvas>
</body>
</html>

Мы написали функцию setGradient (startX, startY, endX, endY), которая создает градиент и устанавливает свойство контекст canvas fillStyle так, чтобы у любой нарисованной закрашенной фигуры был этот градиент.

В функции drawCheckBoard (…) мы вызываем ее непосредственно перед рисованием прямоугольников. Таким образом, каждый прямоугольник рисуется с использованием собственного градиента.

Контекст canvas: радиальные градиенты

Основной принцип/синтаксис: определить два круга при создании градиента.

Радиальные градиенты предназначены для создания градиентов, которые распространяют/интерполируют цвета вдоль кругов, а не распространяют/интерполируют вдоль виртуальной линии, как линейные градиенты.

Вот пример радиального градиента, который интерполирует цвет радуги:

Исходный код:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas {
         border: 1px solid black;
     }
 </style>
  <script>
    var canvas, ctx, grd;

function init() {
   canvas = document.querySelector('#myCanvas');
   ctx = canvas.getContext('2d');
  
   grd = ctx.createRadialGradient(150, 100, 30, 150, 100, 100);
   grd.addColorStop(0, "red");
   grd.addColorStop(0.17, "orange");
   grd.addColorStop(0.33, "yellow");
   grd.addColorStop(0.5, "green");
   grd.addColorStop(0.666, "blue");
   grd.addColorStop(1, "violet");

  draw();
}

function draw() {
  ctx.fillStyle = grd;
  ctx.fillRect(0, 0, 300, 200);
}

  </script>
 </head>
<body onload="init();">   
   <canvas id="myCanvas" width="300" height=200>Your browser does not support the canvas tag.</canvas>
</body>
</html>

Метод из объекта контекста createRadialGradient (cx1, cy1, radius1, cx2, cy2, radius2) принимает в качестве первых трех параметров «начальный» круг градиента, а в качестве трех последних параметров — «конечный круг». В приведенном выше примере градиенты начинаются с окружности, расположенной в точке (150, 100), с радиусом 30, и распространяются на окружность с тем же центром, что и первая (150, 100), но с большим радиусом в 100.

Круги не расположены в одном месте

Можно получить интересные эффекты. Установим центр второго круга в 60 пикселей справа от центра первого круга (cx = 210 вместо 150):

grd = ctx . createRadialGradient ( 150 , 100 , 30 , 210 , 100 , 100 );

Получим такую картинку:

Контекст canvas: узоры/текстуры

Принцип рисования по шаблону основан на повторении изображения (если изображение меньше, чем поверхность фигуры, которую вы собираетесь рисовать) для заполнения поверхности объектов, которые будут нарисованы (либо закрашены, либо зачеркнуты).

Чтобы проиллюстрировать этот принцип, мы будем рисовать прямоугольники, используя этот шаблон: 

Основные шаги рисования:

1.Создайте объект изображения JavaScript:

var  imageObj   new Image ();

2.Определите функцию обратного вызова, которая будет вызываться после полной загрузки изображения в память; мы не можем нарисовать, пока изображение не было загружено:

imageObj onload function () { …}

3.Установите в качестве источника этого изображения URL-адрес шаблона (в нашем примере это URL-адрес шаблона ):

imageObj src «http://www.myserver.com/myRepeatablePattern.png» ;

4.Как только выполняется шаг 3, браузер отправляет HTTP-запрос в фоновом режиме, а когда изображение загружается в память, вызывается обратный вызов, определенный на шаге 2. Мы создаем объект шаблона внутри, из загруженного изображения:

imageObj onload function () { 

pattern1 ctx createPattern imageObj «repeat» );

};

5.Внутри функции обратного вызова (или внутри функции, вызываемой изнутри обратного вызова) мы можем рисовать

  1. // обратный вызов, вызываемый асинхронно, после того, как установлен атрибут src для imageObj
  2. imageObj onload function () {
  3.     pattern1 ctx createPattern imageObj «repeat» );
  4.  
  5.  // Рисуем текстурированный прямоугольник
  6.     ctx FillStyle pattern1 ;
  7.     ctx fillRect 10 10 500 800 );
  8. };

Пример: нарисуем два прямоугольника с рисунком (один заполнен, один обведен):

Исходный код:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
   #myCanvas {
  border:1px solid black;
} 
  </style>
  <script>
   
var canvas, ctx, pattern1;

function init() {
  canvas = document.querySelector('#myCanvas');
  ctx = canvas.getContext('2d');
  
     // нам нужно 1) создать пустой объект изображения, 
     //2) установить функцию обратного вызова; она будет вызвана, когда изображение будет полностью загружено, 
     //3) для создания шаблона объекта, 
     //4) для установки свойства контекста fillStyle или strokeStyle с этим шаблоном, 
     //5) что-то нарисовать
     // МЫ НЕ МОЖЕМ рисовать, пока изображение полностью не загружено -> 
     // только обратный вызов onload!
  
     // Выделить изображение
  var imageObj = new Image();

     // обратный вызов, вызываемый асинхронно, после того, как установлен атрибут src для imageObj
  imageObj.onload = function(){
     // Мы входим сюда только тогда, когда изображение загружено браузером
     // Создание шаблона с использованием объекта изображения
     // Вместо «повторить» попробуйте разные значения: repeat-x, repeat-y, или без повтора, вы можете нарисовать большие фигуры, чтобы увидеть разные результаты
     // Полезно оставлять это как глобальную переменную, если будет использоваться другими функциями
    pattern1 = ctx.createPattern(imageObj, "repeat");
	
    // Рисуем текстурированный прямоугольник
	ctx.fillStyle = pattern1;
	ctx.fillRect(10, 10, 200, 200);
    
    // И каркасный
    ctx.lineWidth=20;
    ctx.strokeStyle=pattern1;
    ctx.strokeRect(230, 20, 150, 100);
};
  
  // Это скажет браузеру отправить асинхронный запрос.
   // Когда браузер получит ответ, обратный вызов будет вызван
  imageObj.src = "https://thumbs.dreamstime.com/x/colourful-flowers-repeatable-pattern-18692760.jpg"; 
}
  </script>
  </head>
<body onload="init();">
    <canvas id="myCanvas" width="500" height="400">
       Your browser does not support the canvas tag.</canvas>
</body>
</html>

Повторяемость шаблона

Вот тот же пример с холстом шириной 1000×1000 пикселей с реализацией повтора шаблона:

Исходный код:

<!DOCTYPE html>
<html >
<head>
  <meta charset="utf-8">
  <style>
   #myCanvas {
  border:1px solid black;
} 
  </style>
  <script>
   
var canvas, ctx, pattern1;

function init() {
  canvas = document.querySelector('#myCanvas');
  ctx = canvas.getContext('2d');
  
  var imageObj = new Image();

  imageObj.onload = function(){
    pattern1 = ctx.createPattern(imageObj, "repeat");
	
	ctx.fillStyle = pattern1;
	ctx.fillRect(10, 10, 500, 800);
    
    ctx.lineWidth=50;
    ctx.strokeStyle=pattern1;
    ctx.strokeRect(650, 20, 300, 800);
};
  
  imageObj.src = "https://thumbs.dreamstime.com/x/colourful-flowers-repeatable-pattern-18692760.jpg"; 
}

  </script>
  </head>
<body onload="init();">
    <canvas id="myCanvas" width="1000" height="1000">
       Your browser does not support the canvas tag.      </canvas>
</body>
</html>

Вы можете изменить способ повторения шаблона, изменив второй параметр этого метода:

pattern1 = ctx . createPattern ( imageObj , " repeat " );

Многократный загрузчик изображений

Для рисования с несколькими узорами нужно загрузить их все перед рисованием. 

Ранее мы говорили, что не можем рисовать до загрузки изображения, используемого шаблоном. Это может быстро усложниться, если нам нужно рисовать с использованием нескольких шаблонов. Нам нужен способ загрузить все изображения, и только когда все изображения будут загружены, начните рисование .

JavaScript — это асинхронный язык. Когда вы устанавливаете атрибут src для изображения, браузер отправляет асинхронный запрос, а затем через некоторое время вызывается обратный вызов onload … Трудность в том, что запросы выполняются параллельно, и мы не знаем, когда и в каком порядке будут загружаться изображения.

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

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

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

Вот пример кода, который дает результат, показанный на картинке:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
 <style>
     #myCanvas {
         border: 1px solid black;
     }
 </style>
 <script>
   var canvas, ctx;
   
   // Список изображений для загрузки, мы использовали объект JavaScript вместо массива, так что именованные индексы (иначе свойства) можно использовать -> проще манипулировать
   var imagesToLoad = {
        flowers: 'http://cdn2.digitalartsonline.co.uk/images/features/1675/intro.jpg',
        lion: 'http://farm3.static.flickr.com/2319/2486384418_8c031fec76_o.jpg',
        blackAndWhiteLys: 'http://pshero.com/assets/tutorials/0062/final.jpg',
        tiledFloor: 'http://4.bp.blogspot.com/_Rqs7w7m37B4/TETj5rD_QmI/AAAAAAAAADk/qRiwoTO-zKk/s1600/symmetry:assymetry+repeatable+pattern.jpg'
      };

 function loadImages(imagesToBeLoaded, drawCallback) {
  var imagesLoaded = {};
  var loadedImages = 0;
  var numberOfImagesToLoad = 0;
   // получаем количество изображений для загрузки 
  for(var name in imagesToBeLoaded) {
    numberOfImagesToLoad++;
  }
    
  for(var name in imagesToBeLoaded) {
    imagesLoaded[name] = new Image();
    imagesLoaded[name].onload = function() {
        if(++loadedImages >= numberOfImagesToLoad) {
           drawCallback(imagesLoaded);
         } 
     }; 
     imagesLoaded[name].src = imagesToBeLoaded[name];
  } 
} 
   
   function init() {
     // This function is called after the page is loaded
     // 1 - Get the canvas
     canvas = document.getElementById('myCanvas');
     // 2 - Get the context
     ctx=canvas.getContext('2d');
     
     loadImages(imagesToLoad, function(imagesLoaded) {
       patternFlowers = ctx.createPattern(imagesLoaded.flowers, 'repeat');
       patternLion = ctx.createPattern(imagesLoaded.lion, 'repeat');
       patternBW = ctx.createPattern(imagesLoaded.blackAndWhiteLys, 'repeat');
       patternFloor = ctx.createPattern(imagesLoaded.tiledFloor, 'repeat');
         drawRectanglesWithPatterns();  
      }); 
   }
   
   function drawRectanglesWithPatterns() {     
     ctx.fillStyle=patternFloor;
     ctx.fillRect(0,0,200,200);
     
     ctx.fillStyle=patternLion;
     ctx.fillRect(200,0,200,200);
     
     ctx.fillStyle=patternFlowers;
     ctx.fillRect(0,200,200,200);

     ctx.fillStyle=patternBW;
     ctx.fillRect(200,200,200,200);
   }
 </script>
 </head>
<body onload="init();">
    <canvas id="myCanvas" width="400" height="400">
            Your browser does not support the canvas tag.
    </canvas>
</body>
</html>

Функция загрузки изображения принимает в качестве параметра список загружаемых изображений и функцию drawCallback, которая будет вызываться только после загрузки всех изображений. Этот обратный вызов принимает в качестве параметра новый объект, который является списком загруженных изображений (см. Строку 34).

Сначала мы подсчитываем количество загружаемых изображений ( строки 26-28), затем для каждого загружаемого изображения создаем новый объект изображения JavaScript ( строка 31) и устанавливаем его атрибут src ( строка 37) — это начнет загружать образ.

Когда приходит изображение, вызывается обратный вызов onload ( строка 32), и внутри мы увеличиваем количество загруженных изображений ( строка 33 ) и проверяем, равно ли это число> = общему количеству изображений, которые должны быть загружены. В этом случае вызывается функция обратного вызова ( строка 34).

Контекст canvas: рисование с тенями

Контекст canvas имеет 4 свойства, которые указывают на то, что мы хотим рисовать фигуры с тенями:

    1. shadowColor : цвет, используемый для теней;
    2. shadowBlur : уровень размытия для теней;
    3. shadowOffsetX : горизонтальное расстояние тени от фигуры;
    4. shadowOffsetY : вертикальное расстояние тени от фигуры.

Простой пример 1: нарисуем заркашенный и обведенный прямоугольник с тенями:

<!DOCTYPE html>
<html>
  <head>
  <script>
   var canvas, ctx;
function init() {
    canvas = document.getElementById('myCanvas');
    ctx = canvas.getContext('2d');
    // вызов функции, которая установит 4 свойства контекста для теней
    setShadow();
    // все рисунки будут отбрасывать тени
    // первый зелёный прямоугольник
    ctx.fillStyle = "#22FFDD"; 
    ctx.fillRect(20, 20, 200, 100);
   // второй контур прямоугольника
    ctx.strokeStyle = "purple"; 
    ctx.lineWidth=10;
    ctx.strokeRect(20, 150, 200, 100);
}
 
// Для ясности мы определяем 4 свойства в отдельной функции
function setShadow() {
    ctx.shadowColor = "Grey";    // цвет    
    ctx.shadowBlur = 20;         // уровень размытия          
    ctx.shadowOffsetX = 15;      // горизонтальное смещение       
    ctx.shadowOffsetY = 15;      // вертикальное смещение       
} 
  </script>
  </head>
<body onload = init();>
    <canvas id="myCanvas" width="400" height =800>
        Your browser does not support the canvas tag.
</canvas>
</body>
</html>

Пример 2: нежелательные тени.

Нарисуем круг с контуром:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
        <style>
            body {
                margin: 0px;
                padding: 0px;
            }
            
            #myCanvas {
                border: 1px solid #9C9898;
            }
        </style>
        <script>
            window.onload = function(){
                var canvas = document.getElementById("myCanvas");
                var ctx = canvas.getContext("2d");
                var centerX = canvas.width / 2;
                var centerY = canvas.height / 2;
                var radius = 70;
                
                ctx.beginPath();
              
                ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false);
              
                ctx.fillStyle = "lightBlue";
                ctx.fill();
              
                ctx.lineWidth = 5;
                ctx.strokeStyle = "black";
             
                ctx.stroke();
              
            };
        </script>
    </head>
    <body>
        <canvas id="myCanvas" width="578" height="200">
        </canvas>
    </body>
</html>

Теперь добавим тень (полный код JS):

<script>
            var canvas, ctx; 
            window.onload = function(){
                canvas = document.getElementById("myCanvas");
                ctx = canvas.getContext("2d");
              
                var centerX = canvas.width / 2;
                var centerY = canvas.height / 2;
                var radius = 70;
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false);
                ctx.fillStyle = "lightBlue";
                addShadows();
                ctx.fill();
                ctx.lineWidth = 5;
                ctx.strokeStyle = "black";
                ctx.stroke();
            };
          // добавляем тень
          function addShadows() {
            ctx.shadowColor = "Grey";    
            ctx.shadowBlur = 20;        
            ctx.shadowOffsetX = 15;     
            ctx.shadowOffsetY = 15;      
          }
  </script>
       

Но полученный результат неудовлетворительный:

Вызов ctx.fill () отбрасывает тень, но вызов ctx.stroke () , который снова рисует весь путь, тоже отбрасывает тень, и на этот раз контур создает нежелательную тень … Как избежать этого эффекта, используя ту же технику для рисования пути?

Хитрость заключается в том, чтобы сохранить контекст перед установкой свойств тени, затем нарисовать закрашенный круг, затем восстановить контекст (в прежнее состояние: без теней), затем нарисовать обведенный круг, вызвав ctx.stroke ().

Вот пример правильной реализации:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
        <style>
            body {
                margin: 0px;
                padding: 0px;
            }
            
            #myCanvas {
                border: 1px solid #9C9898;
            }
        </style>
        <script>
            var canvas, ctx; 
            window.onload = function(){
                canvas = document.getElementById("myCanvas");
                ctx = canvas.getContext("2d");
              
                var centerX = canvas.width / 2;
                var centerY = canvas.height / 2;
                var radius = 70;
                
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false);
              
                //сохранить контекст перед установкой теней и рисованием закрашенного круга
                ctx.save();
              
                // При рисовании пути вы можете изменить контекст свойства, пока не будет выполнен вызов stroke () или fill ()
                ctx.fillStyle = "lightBlue";
              
                // добавляем тени перед рисованием закрашенного круга
                addShadows();
              
                // Рисует закрашенный круг голубым цветом
                ctx.fill();
              
                // восстановить контекст в предыдущее сохраненное состояние
                ctx.restore();
                ctx.lineWidth = 5;
                ctx.strokeStyle = "black";
             
                ctx.stroke();
              
            };
          
          function addShadows() {
            ctx.shadowColor = "Grey";   
            ctx.shadowBlur = 20;       
            ctx.shadowOffsetX = 15;     
            ctx.shadowOffsetY = 15;     
          }
        </script>
    </head>
    <body>
        <canvas id="myCanvas" width="578" height="200">
        </canvas>
    </body>
</html>

Теперь получим корректное изображение:

Стилизация линий

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

Изменение толщины линий мы рассматривали в предыдущей статье.

Стиль линии: изменение заглушек (end caps) для линии

Свойство lineCap контекста указывает способ визуализации заглушек строки. Возможные значения:  butt (default), roundsquare  (сверху вниз на следующей иллюстрации). Обратите внимание, что значение «round» или «square» делает линии немного длиннее, чем значение по умолчанию «butt».

Вот простой пример:

<!DOCTYPE html>
<html>
    <head>
       <meta charset="utf-8">
    </head>
    <body>

        <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas>

        <script>
            var canvas = document.getElementById('myCanvas');
            var ctx = canvas.getContext('2d');

        // первый путь
            ctx.moveTo(20, 20);
            ctx.lineTo(100, 100);
            ctx.lineTo(100, 30);

        // вторая часть пути
            ctx.moveTo(120, 20);
            ctx.lineTo(200, 100);
            ctx.lineTo(200, 30);

       // указать цвет обводки + нарисовать первую часть пути
            ctx.strokeStyle = "#0000FF";
       // Текущая толщина линии 20 пикселей
            ctx.lineWidth = 20;

       // Попробуйте разные значения: butt, round, square
            ctx.lineCap = "round";

            ctx.stroke();
        // Рисуем прямоугольник
            ctx.strokeRect(230, 10, 100, 100);
        </script>

    </body>
</html>

Стиль линии: установка типа угла при встрече двух линий

Свойство lineJoin контекста указывает способ визуализации углов при встрече двух линий. Возможные значения: miter (the default) для создания острых углов, round, или bevel.

Примеры разных типов углов:

<!DOCTYPE html>
<html lang="en">
    <head>
       <meta charset="utf-8">
    </head>
<body>

   <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas>

<script>
  var canvas = document.getElementById('myCanvas');
  var ctx = canvas.getContext('2d');
ctx.moveTo(20,20);
ctx.lineTo(100, 100); 
ctx.lineTo(100,30);
ctx.moveTo(120,20);
ctx.lineTo(200, 100); 
ctx.lineTo(200,30);
ctx.strokeStyle = "#0000FF";
ctx.lineWidth = 20;
// Попробуйте разные значения : miter(default), bevel, round
ctx.lineJoin="round";
ctx.stroke();
// рисуем прямоугольник
ctx.strokeRect(230, 10, 100, 100);
</script>

</body>
</html>

Стиль линии: lineJoin = «miter», свойство miterLimit, способ избежать слишком длинных углов

Значение свойства miterLimit соответствует максимальной длине митры: расстоянию между внутренним углом и внешним углом, где встречаются две линии. Когда угол наклона угла между двумя линиями становится меньше, длина митры увеличивается и может стать слишком большой:

Чтобы избежать этой ситуации, мы можем установить для свойства miterLimit контекста пороговое значение. Если длина митры превышает значение miterLimit , то угол будет отображаться так, как если бы для свойства lineJoin было установлено значение «bevel» (обрезан), а угол был «обрезан» (cut).

<!DOCTYPE html>
<html>
    <head>
      <meta charset="utf-8">
    </head>
    <body>

        <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas>

        <script>
            var canvas = document.getElementById('myCanvas');
            var ctx = canvas.getContext('2d');
            ctx.moveTo(20, 20);
            ctx.lineTo(100, 100);
            ctx.lineTo(100, 30);

            ctx.moveTo(120, 20);
            ctx.lineTo(200, 100);
            ctx.lineTo(200, 30);
            ctx.strokeStyle = "#0000FF";
            ctx.lineWidth = 20;
// попробуйте разные значения: miter(default), bevel, round
            ctx.lineJoin = "miter";
// попробуйте изменить значения на 2, 3, 4, 5 et...
            ctx.miterLimit = 1;
            ctx.stroke();
            ctx.strokeRect(230, 10, 100, 100);
        </script>
    </body>
</html>

Следующая статья «3D-графика с WebGL и ThreeJS«

Читайте больше по теме:

Подписаться
Уведомление о
guest
0 комментариев
Inline Feedbacks
View all comments
Просмотры: 1257

Популярные записи