Tutorial de Canvas – Parte 4 – Aplicando estilos e cores

Este é o quarto artigo de uma série que irá apresentar os recursos disponibilizados pela nova tag <canvas> do HTML5. Nesse artigo, veremos aplicar estilos e cores à objetos desenhados no Canvas. Você pode acessar uma lista de todos os artigos da série nesse link.

Índice do conteúdo

  1. Coress
    1. Um exemplo com fillStyle
    2. Um exemplo com strokeStyle
  2. Transparência
    1. Um exemplo com globalAlpha
    2. Um exemplo usando rgba()
  3. Estilos de linha
    1. Um exemplo com lineWidth
    2. Um exemplo com lineCap
    3. Um exemplo com lineJoin
    4. Uma demonstração da propriedade miterLimit
  4. Gradientes
    1. Um exemplo com createLinearGradient
    2. Um exemplo com createRadialGradient
  5. Padrões
    1. Um exemplo com createPattern
  6. Sombras
    1. Um exemplo de texto sombreado

No artigo que falamos sobre o desenho de formas, usamos apenas as linhas e estilos de preenchimento padrão. Nesse artigo, exploraremos todas as opções que temos a nossa disposição para tornar nossos desenhos mais atraentes.

Cores

Até agora apenas vimos métodos do contexto de desenho. Se quisermos aplicar cores a uma forma, existem duas propriedades importantes que podem usar: fillStyle e strokeStyle.

fillStyle = color
Ajusta o estilo usado ao preencher as formas.
strokeStyle = color
Ajusta o estilo do contorno das formasS.

color é uma String que representa um código CSS para <color>, um objeto de gradiente ou objeto de um padrão. Nós iremos ver sobre gradientes e padrões mais pra frente. Por padrão, as cores do contorne e de preenchimento são ajustadas para preto (valor em CSS da cor = #000000).

Nota: Quando você ajusta a propriedade strokeStyle e/ou fillStyle, o novo valor torna-se o padrão para todas as formas que forem ser desenhadas. Para cada forma que quiser desenhar em uma cor diferente, precisa associar um novo valor para as propriedades fillStyle ou strokeStyle.

As Strings válidas que você pode informar devem ser, de acordo com  especificação, valores CSS3 para <color>. Cada um dos exemplos a seguir descreve a mesma cor.

// these all set the fillStyle to 'orange'

ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";

Nota: Atualmente, nem todos os valores de cores do CSS3 são suportados pelo Gecko. Por exemplo, os valores de cor  hsl(100%,25%,0) ou rgb(0,100%,0) não são permitidos. Se você se ater aos valores acima, não terá nenhum problema.

Nota do tradutor: Para visualizar uma lista de valores de cores, clique nesse link.

Um exemplo com fillStyle

Nesse exemplo, novamente usaremos dois loops for para desenhar um grid de retângulos, cada um com uma cor diferente. A imagem resultante deve parecer com o screenshot abaixo. Não há nada de espetacular acontecendo aqui. Usamos suas variáveis i e j para gerar uma cor RGB única para cada quadrado, e apenas modificamos os valores para o vermelho e para o verde. Ao modificar esses canais, você pode gerar todos os tipos de paleta. Ao aumentar os passos, você pode conseguir algo similar a paleta de cores usada pelo Photoshop.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i=0;i<6;i++){
    for (var j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
}

O resultado será o seguinte:

Screenshot Live sample

Um exemplo com strokeStyle

Esse exemplo é similar ao anterior, mas usa a propriedade strokeStyle para alterar as cores do contorno das formas. Usamos o método arc() para desenhar círculos ao invés de quadrados.

  function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');
    for (var i=0;i<6;i++){
      for (var j=0;j<6;j++){
        ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
                         Math.floor(255-42.5*j) + ')';
        ctx.beginPath();
        ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
        ctx.stroke();
      }
    }
  }

O resultado deve ser o seguinte:

Screenshot Live sample

Transparência

Além do desenho de formas opacas no canvas, podemos também desenhar formas semi-transparentes ou translúcidas. Isso é feito pela definição da propriedade globalAlpha ou associando uma cor semi-transparente ao estilo do contorno ou do preenchimento.

globalAlpha = transparencyValue
Aplica o valor de transparência à todas as formas que serão desenhadas futuramente no canvas. O valor precisa estar entre  0.0 (totalmente transparente) à 1.0 (totalmente opção). O valor 1.0 (totalmente opaco) é o padrão.

A propriedade globalAlpha pode ser útil se quiser desenhar várias formas no canvas com transparência similar, mas normalmente é mais útil configurar a transparência de formas individuais quando se ajusta suas cores.

Como as propriedades strokeStyle e fillStyle aceitam valores de cores CSS3, podemos usar a seguinte notação para definir a transparência da cor:

// Assigning transparent colors to stroke and fill style

ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";

A função rgba() é parecida com a função rgb() mas possui um parâmetro extra. O último parâmetro ajusta a transparência dessa cor em particular. A faixa válida de valores é, novamente, entre 0.0 (totalmente transparente) e 1.0 (totalmente opaco).

Um exemplo com globalAlpha

Nesse exemplo, um fundo de quatro diferentes quadrados coloridos. Em cima deles, desenharemos um conjunto de círculos semi-transparentes, A propriedade globalAlpha será ajustada para 0.2 que será usada para todas as formas dessa ponto em diante, Cada passo do loop for desenha um conjunto de círculos com um raio crescente. O resultado final é um gradiente radial. Ao sobrepor cada vez mais círculos um sobre o outro, reduzimo efetivamente a transparência dos círculos que já foram desenhados. Ao aumentar o contador e, assim, desenhando mais círculos, o fundo desaparece completamente no centro da imagem.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  // draw background
  ctx.fillStyle = '#FD0';
  ctx.fillRect(0,0,75,75);
  ctx.fillStyle = '#6C0';
  ctx.fillRect(75,0,75,75);
  ctx.fillStyle = '#09F';
  ctx.fillRect(0,75,75,75);
  ctx.fillStyle = '#F30';
  ctx.fillRect(75,75,150,150);
  ctx.fillStyle = '#FFF';

  // set transparency value
  ctx.globalAlpha = 0.2;

  // Draw semi transparent circles
  for (i=0;i<7;i++){
    ctx.beginPath();
    ctx.arc(75,75,10+10*i,0,Math.PI*2,true);
    ctx.fill();
  }
}
Screenshot Live sample

Um exemplo usando rgba()

Nesse segundo exemplo, faremos algo similar ao exemplo acima, mas ao invés de desenhar círculos um sobre o outro, desenharemos quadrados com opacidade crescente. O uso da função rgba() dá a você um pouco mais de controle e flexibilidade pois podemos ajustar o preenchimento e o contorno individualmente.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // Draw background
  ctx.fillStyle = 'rgb(255,221,0)';
  ctx.fillRect(0,0,150,37.5);
  ctx.fillStyle = 'rgb(102,204,0)';
  ctx.fillRect(0,37.5,150,37.5);
  ctx.fillStyle = 'rgb(0,153,255)';
  ctx.fillRect(0,75,150,37.5);
  ctx.fillStyle = 'rgb(255,51,0)';
  ctx.fillRect(0,112.5,150,37.5);

  // Draw semi transparent rectangles
  for (var i=0;i<10;i++){
    ctx.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')';
    for (var j=0;j<4;j++){
      ctx.fillRect(5+i*14,5+j*37.5,14,27.5)
    }
  }
}
Screenshot Live sample

Estilos de linha

Existem diversas propriedades que nos permitem definir o estilo das linhas.

lineWidth = value
Ajusta a largura das linhas desenhas a partir desse ponto em diante.
lineCap = type
Ajusta a aparência do final das linhas.
lineJoin = type
Ajusta a aparência dos “cantos” onde as linhas se encontram.
miterLimit = value
Estabelece um limite da esquadria (miter) quando duas linhas se juntam em um ângulo agudo, para permitir que você controle quão denso a junção será.

Você entenderá melhor do que cada propriedade faz através da observação dos exemplos a seguir.

Um exemplo com lineWidth

Essa propriedade ajusta a espessura atual da linha. Os valores devem ser números positivos. Por padrão esse valor é ajustada para 1.0 unidade.

A largura da linha é a espessura do contorno centralizado em um caminho dado. Em outras palavras, a área que é desenhada e estende-se para metade da largura da linha em qualquer lado da caminho. Como as coordenadas do canvas não referenciam diretamente pixels, cuidados especiais devem ser tomados para obter linhas horizontais e verticais limpas.

No exemplo a seguir, 10 linhas retas são desenhadas com larguras crescentes. A linha da extremidade mais à esquerda tem largura 1.0. Porém, as outras linhas que tem uma densidade com um valor inteiro diferente não aparentam pureza, por causa do posicionamento do caminho.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i = 0; i < 10; i++){
    ctx.lineWidth = 1+i;
    ctx.beginPath();
    ctx.moveTo(5+i*14,5);
    ctx.lineTo(5+i*14,140);
    ctx.stroke();
  }
}
Screenshot Live sample

Obter linhas limpas requer conhecimento de como os caminhos são feitos. Nas imagens abaixo, o grid representa o grid de coordenadas do canvas. Os quadrados entre as as linhas do grid são pixels da tela. Na imagem do primeiro grid, um retângulo de (2,1) a (5,5) é preenchido. Toda a área entre os pontos (linha vermelha) está dentro dos limites do pixel, de forma que o retângulo resultante terá cantos limpos.

Se você considerar um caminho de (3,1) a (3,5) com uma espessura de linha de 1.0, terminará com a situação da segunda imagem. A área a ser preenchida (azul escuro) se estende apenas a metade dos pixels em cada lado no caminho. Uma aproximação disso precisa ser renderizada, o que significa que pixels estão sendo parcialmente sombreados, e o resultado é que toda a área (azul claro e azul escuro) será preenchida com uma cor apenas metade escura em relação a cor do contorno. Isso é o que acontece com a espessura de linha 1.0 no exemplo anterior.

Para corrigir isso, temos que ser bastante precisos na criação de nosso caminho. Sabendo que uma largura de 1.0 irá se estender meia unidade para cada lado do caminho, criar um caminho de (3.5,1) a (3.5,5) resulta na situação da terceira imagem – a linha de espessura 1.0 acaba preenchendo completamente e precisamente o espaço de um único pixel vertical.

Nota: Esteja ciente de que em nosso exemplo de linhas verticais, a posição Y ainda se refere a uma posição inteira no grid – se não fosse assim, veríamos pixels com metade da cobertura nos endpoints (mas observe que esse comportamento depende do estilo definido em lineCap cujo valor padrão é butt; você pode querer computar contornos consistentes com coordenadas de meio pixel para linhas com larguras estranhas, pela definição de lineCap para square, de forma que as bordas externas do contorno em torno do endpoint sejam automaticamente estendidas para cobrir exatamente o pixel inteiro.

Observe também que apenas os endpoints iniciais e finais de um caminho são afetados: se um caminho for fechado com closePath(), não haverá um endpoint inicial ou final; ao invés disso, todos os endpoints do caminho serão conectados aos seus segmentos anteriores e posteriores usando o ajuste atual de lineJoin, cujo valor padrão é miter, tendo como efeito a extensão automática das bordas externas dos segmentos conectados para seus pontos de itersecção, de forma que os contornos renderizados irão cobrir exatamente os pixels centralizados em cada endpoint se estes segmentos forem horizontais e/ou verticais. Veja as próximas seções para demonstrações desses estilos de linhas adicionais.

Para linhas de mesma largura, cada metade acaba sendo uma quantidade inteira de pixels, de forma que você quer um caminho que esteja esteja entre os pixels (3,1) e (3,5), ao invés de algo no meio desses valores.

Enquanto é um pouco doloroso se trabalhar com gráficos 2D escaláveis inicialmente, prestando atenção ao grid de pixels e à posição dos caminhos garante que seus desenhos irão parecer corretos não importando a escala ou qualquer outra transformação envolvida. Uma linha vertical de largura 1.0 desenhada na posição correta tornará-se uma linha limpa de 2 pixels quando escalonada em 2 unidades, e aparecerá na posição correta.

Um exemplo com lineCap

A propriedade lineCap determina como os endpoints de cada linha serão desenhados. Existem três valores para essa propriedade, que são: butt, round e square. Por padrão, o valor dessa propriedade é butt.

butt
Os finais da linha são retos nos endpoints.
round
Os finais da linha são arredondados.
square
Os finais da linha são retos pelo acréscimo de uma caixa com uma largura igual e metada da altura da espessura da linha.

Nesse exemplo, desenhamos três linhas, cada uma com um valor diferente para a propriedade lineCap. Também é recomendado adicionar dois guias para visualizar as diferenças exatas entre as três. Cada uma dessas linhas inicia e termina exatamente nessas guias.

A linha a esquerda usa a opção padrão butt. Você observará que ela é desenhada completamente nivelada com as guias. A segunda usa a opção round. Isso adiciona um semi-circulo no final cujo raio é metade da espessura da linha. A linha à direita usa a opção square. Isso adiciona uma caixa com uma largura igual e cuja altura é metade da espessura da linha.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var lineCap = ['butt','round','square'];

  // Draw guides
  ctx.strokeStyle = '#09f';
  ctx.beginPath();
  ctx.moveTo(10,10);
  ctx.lineTo(140,10);
  ctx.moveTo(10,140);
  ctx.lineTo(140,140);
  ctx.stroke();

  // Draw lines
  ctx.strokeStyle = 'black';
  for (var i=0;i<lineCap.length;i++){
    ctx.lineWidth = 15;
    ctx.lineCap = lineCap[i];
    ctx.beginPath();
    ctx.moveTo(25+i*50,10);
    ctx.lineTo(25+i*50,140);
    ctx.stroke();
  }
}
Screenshot Live sample

Um exemplo de lineJoin

A propriedade lineJoin determina como dois segmentos que se conectam (de linhas, arcos ou curvas) com comprimentos não nulos em uma forma são conectados entre si (segmentos degenerados com comprimento nulo, cujos endpoints especificados e pontos de controle estão exatamente na mesma posição, são ignorados).

Existem três valores possíveis para essa propriedade: round, bevel e miter. Por padrão, o valor dessa propriedade é miter. Observe que o ajuste dessa propriedade não tem nenhum efeito se os dois segmentos tiverem a mesma direção, porque nesse caso nenhuma área de junção será adicionada.

round
Arredonda os cantos de um forma pelo preenchimento de um setor adicional de disco centralizado no endpoint comum aos segmentos. O raio desses cantos é igual a largura da linha.
bevel
Preenche uma área triangular adicional entre os endpoints comuns dos segmentos, e os cantos retangulares externos e separados de cada segmento.
miter
Segmentos que se conectam são juntados pela extensão de seus cantos externos para conecta-los em um único ponto, com o efeito de preencher uma área em forma de um losango adicional. Esse ajuste é afetada pela propriedade miterLimit que é explicada a seguir.

O exemplo a seguir desenha três caminhos diferentes, demonstrando cada um desses ajustes da propriedades lineJoin; a saída é mostrada logo abaixo.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var lineJoin = ['round','bevel','miter'];
  ctx.lineWidth = 10;
  for (var i=0;i<lineJoin.length;i++){
    ctx.lineJoin = lineJoin[i];
    ctx.beginPath();
    ctx.moveTo(-5,5+i*40);
    ctx.lineTo(35,45+i*40);
    ctx.lineTo(75,5+i*40);
    ctx.lineTo(115,45+i*40);
    ctx.lineTo(155,5+i*40);
    ctx.stroke();
  }
}
Screenshot Live sample

Uma demonstração da propriedade miterLimit

Como foi visto no exemplo anterior, quando se une duas linhas com a opção miter, os cantos externos das duas linhas que se unem são estendidas até o ponto onde elas de unem. Para linhas que estão a grandes distâncias angulares umas da outras, esse ponto não fica longe do ponto de conexão. Porém, a medida que esse ângulo entre as linhas diminui, a distância (comprimento do esquadro – miter) entre esses pontos aumenta exponencialmente.

A propriedade miterLimit determina quão distantes os pontos de conexão externos podem ser posicionados a partir dos pontos de conexão internos. Se duas linhas excederem esses valor, uma junta bisel é desenhada no lugar. Observe que o comprimento máximo do esquadro (miter)  é o produto da largura da linha medida no sistema de coordenadas atual, pelo valor de sua propriedade miterLimit (cujo valor padrão é 10.0 no .<canvas>do HTML), de forma que miterLimit pode ser configurada independentemente da escala de exibição atual ou qualquer transformação afim: ele influencia apenas forma efetivamente renderizada dos cantos da linha.

Mais exatamente, o limite do esquadro é a maior taxa de extensão do comprimento permitido (no canvas do HTML, é a medida entre o canto externo dos cantos juntados da linha e o endpoint comum dos segmentos que se conectam) para metade da espessura da linha. Ele pode ser definido de forma equivalente como a maior razão permitida da distância entre os pontos de junção internos e externos, para a espessura total da linha. É igual, finalmente, ao cosecante da metade do menor ângulo interno dos segmentos que se conectam abaixo, onde nenhuma junção será renderizada, apenas um chanfro:

  • miterLimit = max miterLength / lineWidth = 1 / sin ( min θ / 2 )
  • O limite padrão do esquadro (miter) de 10.0 irá tirar todos os esquadros de ângulos agudos menores que 11 graus.
  • Um limite de esquadro (miter) igual a √2 ≈ 1.4142136 (arredondado) irá tirar todos os ângulos agudos, mantendo as juntas dos esquadros apenas para ângulos obtusos ou retos.
  • Um limite de esquadro (miter) igual a 1.0 é válido mas irá desabilitar todos os esquadros.
  • Valores menores que 1.0 são inválidos.

A seguir uma pequena demonstração na qual você pode ajustar dinamicamente a propriedade miterLimit e visualizar como isso afeta as formas no canvas. As linhas azuis mostram onde os endpoints começam e terminam para cada uma das linhas no zig-zag.

Se você especificar um valor de 4.2 para miterLimit nessa demonstração, nenhum dos cantos visíveis irá se juntar com uma extensão do esquadro, mas apenas um pequeno chanfro próximo as linhas azuis; com um miterLimit acima de 10, muitos cantos dessa demonstração devem se juntar com um esquadro bem distante das linhas azuis, e seus tamanhos estarão diminuindo entre os cantos da esquerda para a direita porque eles se conectam com ângulos maiores; com valores intermediários, os cantos do lado esquerdo irão de juntar com um chanfro próximo as linhas azuis, e os do lado direito com uma extensão do esquadro (também com comprimento decrescente).

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // Clear canvas
  ctx.clearRect(0,0,150,150);

  // Draw guides
  ctx.strokeStyle = '#09f';
  ctx.lineWidth   = 2;
  ctx.strokeRect(-5,50,160,50);

  // Set line styles
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 10;

  // check input
  if (document.getElementById('miterLimit').value.match(/\d+(\.\d+)?/)) {
    ctx.miterLimit = parseFloat(document.getElementById('miterLimit').value);
  } else {
    alert('Value must be a positive number');
  }

  // Draw lines
  ctx.beginPath();
  ctx.moveTo(0,100);
  for (i=0;i<24;i++){
    var dy = i%2==0 ? 25 : -25 ;
    ctx.lineTo(Math.pow(i,1.5)*2,75+dy);
  }
  ctx.stroke();
  return false;
}
Screenshot Live sample

Gradientes

Como em qualquer programa de desenho normal, podem preencher e contornar as formas usando gradientes lineares ou radiais. Criamos um objeto canvasGradient pelo uso de um dos seguintes métodos. Podemo assim associar esse objetos às propriedades fillStyle ou strokeStyle.

createLinearGradient(x1, y1, x2, y2)
Crie um objeto de gradiente linear com ponto inicial em (x1, y1) e ponto final em (x2, y2).
createRadialGradient(x1, y1, r1, x2, y2, r2)
Cria um gradiente radial. Os parâmetros representam dois círculos, um com centro em (x1, y1) e raio r1, e o outro com centro em (x2, y2) com raio em r2.

Por exemplo:

var lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
var radialgradient = ctx.createRadialGradient(75, 75, 0, 75, 75, 100);

Uma vez que tenhamos criado um objeto canvasGradient podemos associar cores a ele usando o método addColorStop().

gradient.addColorStop(position, color)
Cria um novo ponto de cor para o objeto gradient. O parâmetro position é um número entre 0.0 e 1.0 e define a posição relativa da cor no gradiente, e o argumento  color precisa ser uma String que represente um código CSS para <color>, indicando a cor que o gradiente deve alcançar no intervalo da transição.

Você pode adicionar quantos pontos de cor quiser a um gradiente. Abaixo segue um exemplo bastante simples de gradiente linear da cor branca para  a preta.

var lineargradient = ctx.createLinearGradient(0,0,150,150);
lineargradient.addColorStop(0, 'white');
lineargradient.addColorStop(1, 'black');

Um exemplo com createLinearGradient

Nesse exemplo, criaremos dois gradientes diferentes. Como pode ver, tanto a propriedade strokeStyle quanto a propriedade fillStyle aceitam o objeto canvasGradient como entrada válida.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // Create gradients
  var lingrad = ctx.createLinearGradient(0,0,0,150);
  lingrad.addColorStop(0, '#00ABEB');
  lingrad.addColorStop(0.5, '#fff');
  lingrad.addColorStop(0.5, '#26C000');
  lingrad.addColorStop(1, '#fff');

  var lingrad2 = ctx.createLinearGradient(0,50,0,95);
  lingrad2.addColorStop(0.5, '#000');
  lingrad2.addColorStop(1, 'rgba(0,0,0,0)');

  // assign gradients to fill and stroke styles
  ctx.fillStyle = lingrad;
  ctx.strokeStyle = lingrad2;

  // draw shapes
  ctx.fillRect(10,10,130,130);
  ctx.strokeRect(50,50,50,50);

}

O primeiro é um gradiente de fundo. Como pode ver, associamos duas cores na mesma posição. Você faz isso para criar transições de cores bastante agudas – nesse caso, do branco para o verde; Normalmente não importa em que ordem você define os pontos de cor, mas nesse caso em especial, importa significativamente; Se você manter as associações na ordem em que quer que elas apareçam, isso não será um problema.

No segundo gradiente, não associamos uma cor inicial (na posição 0.0) pois isso não é estritamente necessário, porque será assumida automaticamente a cor do próximo do ponto. Por isso, associando a cor preta à posição 0.5 automaticamente faz o gradiente, do início até esse ponto, preto.

Screenshot Live sample

Um exemplo com createRadialGradient

Nesse exemplo, definiremos gradientes radiais; Por termos controle sobre os pontos de início e termino do gradiente, podem conseguir efeitos mais complexos do que normalmente conseguiríamos em gradientes radiais “clássicos” já vistos, por exemplo, no Photoshop (isto é, um gradiente com um ponto central único de onde o gradiente se expande em uma forma circular).

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // Create gradients
  var radgrad = ctx.createRadialGradient(45,45,10,52,50,30);
  radgrad.addColorStop(0, '#A7D30C');
  radgrad.addColorStop(0.9, '#019F62');
  radgrad.addColorStop(1, 'rgba(1,159,98,0)');

  var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50);
  radgrad2.addColorStop(0, '#FF5F98');
  radgrad2.addColorStop(0.75, '#FF0188');
  radgrad2.addColorStop(1, 'rgba(255,1,136,0)');

  var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40);
  radgrad3.addColorStop(0, '#00C9FF');
  radgrad3.addColorStop(0.8, '#00B5E2');
  radgrad3.addColorStop(1, 'rgba(0,201,255,0)');

  var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90);
  radgrad4.addColorStop(0, '#F4F201');
  radgrad4.addColorStop(0.8, '#E4C700');
  radgrad4.addColorStop(1, 'rgba(228,199,0,0)');

  // draw shapes
  ctx.fillStyle = radgrad4;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad3;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad2;
  ctx.fillRect(0,0,150,150);
  ctx.fillStyle = radgrad;
  ctx.fillRect(0,0,150,150);
}

Nesse caso, deslocamos o ponto inicial levemente do ponto final para conseguir uma efeito 3D esférico. É melhor evitar que os círculos internos e externos se sobreponham pois isso resulta em efeitos estranhos difíceis de serem previstos.

O último ponto de cor dos quatro gradientes usa uma cor totalmente transparente. Se quiser ter uma transição legal deste ponto para o anterior, as duas cores devem ser iguais. Isso não é muito óbvio no código porque ele usa dois métodos de cor CSS diferentes para efeitos de demonstração, mas no primeiro gradiente #019F62 = rgba(1,159,98,1).

Screenshot Live sample

Padrões

Em um dos exemplos do artigo anterior, usamos uma série de loops para criar um padrão de imagens. Existe, porém, um método muito mais simples: o método createPattern().

createPattern(image, type)
Crie e retorna um novo objeto de padrão.image é um CanvasImageSource (isto é, um HTMLImageElement, outro canvas, um elemento <video>, ou o que preferir). type é uma String que indica como usar essa imagem.

O tipo especifica como usar a imagem de forma a criar o padrão, e precisa ter um dos seguintes valores:

repeat
Ladrilha a imagem tanto nas direções verticais quanto horizontais.
repeat-x
Ladrilha a imagem horizontalmente mas não verticalmente.
repeat-y
Ladrilha a imagem verticalmente mas não horizontalmente.
no-repeat
Não ladrilha a imagem. Ela é usada apenas uma vez.

Nota: O Firefox suporta apenas a propriedade repeat. Se você usar qualquer outra, não verá nenhuma mudança.

Nota: Ao especificar um canvas cujas dimensões sejam 0x0 pixels um erro será disparado. Usamos esse método para criar um objetoCanvasPattern de forma análoga aos métodos para criar gradientes visto acima. Uma vez que tenhamos criado um padrão, podemos associa-lo as proprieddes fillStyle ou strokeStyle. Por exemplo:
var img = new Image();
img.src = 'someimage.png';
var ptrn = ctx.createPattern(img,'repeat');

Nota: Como o método drawImage() , você deve se certificar que a imagem que você está usando esteja carregada antes de chamar esse método ou o padrão pode ser desenhado incorretamente.

Um exemplo com createPattern

Nesse último exemplo, criaremos um padrão para ser associada à propriedade fillStyle. A única coisa que vale a pena ser mencionada é o uso do manipulador onload de image. Isso é feito para garantir que a imagem tenha sido carregada antes dela ser associada ao padrão.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // create new image object to use as pattern
  var img = new Image();
  img.src = '/files/222/Canvas_createpattern.png';
  img.onload = function(){

    // create pattern
    var ptrn = ctx.createPattern(img,'repeat');
    ctx.fillStyle = ptrn;
    ctx.fillRect(0,0,150,150);

  }
}
Screenshot Live sample

Sombras

O uso de sombras envolve apenas quatro propriedades:

shadowOffsetX = float
Indica a distância horizontal que a sombra deve tomar do objeto. Esse valor não é afetado pela matriz de transformação. O padrão é 0.
shadowOffsetY = float
Indica a distância vertical que a sombra deve tomar do objeto. Esse valor não é afetado pela matriz de transformação. O padrão é 0.
shadowBlur = float
Indica o tamanho do efeito de obscurecimento; esse valor não corresponde ao número de pixel e não é afetado pela matriz de transformação. O padrão é 0.
shadowColor = <color>
Um valor de cor CSS padrão que indica a cor do efeito de sombreamento; por padrão, é preto totalmente transparente.

shadowOffsetX e shadowOffsetY indicam quão longe a sombra deve se estender do objeto nas direções X e Y; esses valores não são afetados pela matriz de transformação atual. Use valores negativos para fazer com que a sombra se estenda para cima ou para a esquerda, e valores positivos para fazer com que se estenda para baixo ou para a direita. Eles são 0 por padrão.

shadowBlur indica o tamanho do efeito de sombreamento; esse valor não corresponde ao número de pixel e não é afetado pela matriz de transformação. O padrão é 0.

shadowColor é um valor de cor CSS padrão que indica a cor do efeito de sombreamento; por padrão, é preto totalmente transparente.

Um exemplo de texto sombreado

Esse exemplo desenha uma String de texto com um efeito de sombreamento.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 2;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

  ctx.font = "20px Times New Roman";
  ctx.fillStyle = "Black";
  ctx.fillText("Sample String", 5, 30);
}
Screenshot Live sample