Advertisement
  1. Photo & Video
  2. JavaScript

Quem Precisa do AMP? Como Carregar Imagens Responsivas Sob Demanda de Forma Rápida e Fácil com Layzr.js

Scroll to top
Read Time: 15 min

() translation by (you can also view the original English article)

O projeto "Páginas Mobile Aceleradas" (AMP) do Google tem, recentemente, influenciado sites a ficarem mais rápidos. Com boa técnica e uma rede de distribuição de conteúdo poderosa, Google tornou sites usando AMP mais rápidos. AMP também trabalhou indiretamente ao encorajar-nos olhar as otimizações e boas práticas que ele contem. Mesmo que não vá tornar seu site compatível com AMP, é útil entender o AMP como uma lista de otimização para sites que não o aplicarão.

Uma das otimizações na lista é a técnica chamada "carregamento sob demanda", que vimos em ação num artigo recente usando o elemento customizado <amp-img> do AMP. Com essa técnica, quando alguém visita pela primeira vez uma página, apenas a imagem na área de exibição ou próxima a ela, será exibida. As outras serão carregadas de acordo com a rolagem.

Carregar sob demanda permite visitantes interagirem com conteúdo mais cedo, e a velocidade de carga aprimorada pode melhorar a posição nas buscas. Quanto mais imagens na página, maior o aprimoramento em velocidade a ser obtido.

Nesse tutorial mostraremos como usar imagens sob demanda em sites não AMP usando o Layzr.js. Replicaremos a funcionalidade do elemento <amp-img> do AMP o mais fiel possível, mas também trabalharemos com alguns dos recursos específicos do Layzr.

Comecemos!

1. Configuração Básica

Como parte do artigo "Projeto AMP: Seus Sites Ficarão Mais Rápidos?" criamos um layout básico contendo cinco imagens. Para poder comparar o AMP com a técnica que aplicaremos, recriaremos o mesmo layout com cinco imagens. Mostraremos como usar várias velocidades de carga nos testes mais tarde, nesse tutorial.

No arquivos fonte anexos ao tutorial, pode-se encontrar a versão AMP do layout e a versão completa do que faremos aqui. Ambas foram incluídas para ajudar a decidir qual a melhor abordagem para você.

Recomendamos testar usando a Ferramenta de Desenvolvedores do Chrome (F12) com a aba Network aberta, Desabilitar Cache habilitado e gargalo configurado como Regular 3G. Isso simulará uma conexão mobile média, mostrando um gráfico de cada imagem carregada, em tempo real, ajudando a ter uma visão clara do funcionamento do carregamento sob demanda.

Ao recarregar a página para testar, segure o botão de recarregar e uma lista com diferentes opções aparecerá. Escolha Limpar Cache e Recarregar do Zero para simular um visitante chegando ao site pela primeira vez.

Emty cache and hard reload in Chrome Developer ToolsEmty cache and hard reload in Chrome Developer ToolsEmty cache and hard reload in Chrome Developer Tools

Crie o HTML Base

Comecemos com o básico. Primeiro, criemos um diretório para o projeto e, dentro dele, criemos um arquivo chamado index.html.

Abra-o e adicione o código abaixo:

1
<!doctype html>
2
<html lang="en">
3
  <head>
4
    <meta charset="utf-8">
5
    <title>Layzr.js Lazy Loading</title>
6
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
7
    <style>
8
    body {
9
      margin: 0;
10
    }
11
    img {
12
      display: block;
13
      margin: 0 auto;
14
    }
15
    </style>
16
  </head>
17
  <body>
18
    <h1>Welcome to the lazy loaded web</h1>
19
20
21
22
  </body>
23
</html>

Com esse código, criamos um HTML base e incluímos um pouco de CSS para garantir que o body e as imagens da página não tenham espaço entre si.

Também incluímos margin: 0 auto; para as imagens adicionadas centralizarem.

Carregue Layzr

O script layzr.js tem duas fontes CDN que podemos usar—usaremos a do Cloudfare.

Adicione esse código ao seu HTML, antes da tag </body>.

1
<script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.2/layzr.min.js"></script>

Se preferir não usar um CDN, podemos baixar o arquivo e seguir as instruções presentes em: https://github.com/callmecavs/layzr.js#download

Instancie Layzr

Agora que temos Layzr adicionado, temos que executá-lo quando a página carregar. Para isso, coloque esse código após a tag script adicionada no passo anterior:

1
<script>
2
const instance = Layzr()
3
4
document.addEventListener('DOMContentLoaded', function(event){
5
  instance.update().check().handlers(true)
6
})
7
</script>

Esse código criará uma instância do Layzr e, assim que o conteúdo da DOM finalizar o carregamento, usa-la-á para ativar as funcionalidades.

Seu código deve estar mais ou menos assim, agora:

1
<!doctype html>
2
<html lang="en">
3
  <head>
4
    <meta charset="utf-8">
5
    <title>Layzr.js Lazy Loading</title>
6
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
7
    <style>
8
    body {
9
      margin: 0;
10
    }
11
    img {
12
      display: block;
13
      margin: 0 auto;
14
    }
15
    </style>
16
  </head>
17
  <body>
18
    <h1>Welcome to the lazy loaded web</h1>
19
20
21
22
    <script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.2/layzr.min.js"></script>
23
    <script>
24
    const instance = Layzr()
25
26
    document.addEventListener('DOMContentLoaded', function(event){
27
      instance.update().check().handlers(true)
28
    })
29
    </script>
30
31
  </body>
32
</html>

2. Adicione Imagens (Resolução Normal)

Com Layzr carregado e pronto para seguir, podemos começar a adicionar algumas imagens para usar sua mágica. Podemos usar quaisquer imagens, contudo, se quiser usar os mesmo códigos do exemplo, pode-se baixar os arquivos fontes anexos ao tutorial. Lá, é possível encontrar um diretório images que pode ser copiado para qualquer outro projeto.

Para adicionar imagens usando Layzr, usaremos o elemento img padrão, mas ao invés do atributo src, usaremos o data-normal, assim:

1
<img data-normal="images/vulture.jpg" alt="Vulture">

Garanta Que Imagens tem Altura

Para qualquer script de carga sob demanda funcionar, é preciso que saibam a altura das imagens do site para poder decidir quais precisam ser carregadas (elas estão na área de visualização ou próximas?) e quais devem esperar.

A complicação, porém, é que uma imagem não possui altura até que esteja totalmente carregada na página. Isso significa que se quisermos que a carga sob demanda funcione, precisamos dar à pagina a altura das imagens antes que elas sejam carregadas.

Cobriremos dois métodos: um com imagens de tamanho fixo e outro para imagens responsivas. Dar altura a imagens, tornando-as fixas, é o método mais simples, já que basta adicionar os atributos height e width.

1
<img data-normal="images/vulture.jpg" alt="Vulture" height="640" width="960">

Vá em frente e adicione os elementos img antes das tags script, usando o atributo data-normal e inclua height e width para cada imagem que quisr carregar.

1
    <img data-normal="images/vulture.jpg" alt="Vulture" height="640" width="960">
2
3
    <img data-normal="images/beach.jpg" alt="Beach" height="640" width="960">
4
5
    <img data-normal="images/bear.jpg" alt="Bear" height="640" width="960">
6
7
    <img data-normal="images/sky.jpg" alt="Sky" height="540" width="960">
8
9
    <img data-normal="images/bike.jpg" alt="Bike" height="540" width="960">

Esse método permitirá a carga sob demanda funcionar, mas prevenirá a responsividade das imagens e isso não é o ideal. Cobriremos como dar altura e responsividade a imagens mais tarde.

3. Configure um Limite de Carregamento

Por padrão, Layzr apenas carrega as imagens na área de visualização na hora da carga. Todavia, visitantes terão uma experiência mais suave se as imagens próximas (fora da área de visão) estejam pré-carregadas também.

Obtenha isso configurando a opção threshold (limite) à instância do script. Seu funcionamento é assim: dá-se um valor representando um percentual da área de visualização. Se o valor for 100, equivalerá a 100% da área de visualização, por exemplo, 1200px. Nesse caso, qualquer coisa fora da área de visualização mas até 1200px de distância, também seria carregado.

Por exemplo, se tivermos duas imagens grandes e uma delas estiver fora da área de visualização, e o limite for 100, ambas imagens carregarão:

Para configurar um limite, altere essa linha no código:

1
const instance = Layzr()

...com isso:

1
const instance = Layzr({
2
  threshold: 100
3
})

Pode-se alterar esse valor para qualquer outro que funcionar para o site que criar. Como ponto interessante, parece que o limite de carga sob demanda do AMP é equivalente a 200.

4. Adicione Images HiDPI/Retina

Algo interessante do Layzr é que é bem simples adicionar imagens para aparelhos com alta resolução. Tudo que precisa fazer é adicionar o atributo data-retina. Por exemplo:

1
<img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" height="640" width="960">

Atualize todos os elmentos img no HTML para incluir imagens retina, dessa forma:

1
    <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" height="640" width="960">
2
3
    <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach" height="640" width="960">
4
5
    <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear" height="640" width="960">
6
7
    <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky" height="540" width="960">
8
9
    <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike" height="540" width="960">

5. Marcador de Imagem Responsiva e Prevenção de Refluxo

Tornar imagens sob demanda responsivas pode ser bem complexo. Como mencionamos antes, para determinar quando carregar as imagens, Layzr precisa saber suas alturas. Como imagens responsivas mudam suas dimensões o tempo todo, suas alturas são imprevisíveis.

Alé disso, também precisamos de algo na página para prevenir seu refluxo. Refluxo acontece quanto uma imagem termina de carregar e sai do estado sem tamanho e começa a ocupar espaço no layout, fazendo tudo ao seu redor se mover. Isso pode ser bem frustrante para os visitantes que tentam focar no conteúdo enquanto ele se move pela página.

Podemos resolvê-los com marcadores responsivos na página no tamanho correto de cada imagem. Os marcadores garantirão que a página não tenha refluxo e também darão alturas para o Layzr trabalhar. Basearemos nossa abordagem na engenhosa técnica de um artigo do "A List a Part", por Thierry Kobletz, sobre "Criar Proporções Intrínsecas para Vídeos".

A única condição é saber, antecipadamente, a proporção de cada imagem publicada, porque o CSS redimensionará cada uma de acordo com essa proporção.

Adicione Container de Proporção de Tela

A primeira coisa que faremos é adicionar uma div container para a primeira imagem—essa div será nosso marcador. Redimensionaremos a div com CSS e configuraremos a imagem para prenchê-la vertical e horizontalmente.

Daremos à div uma classe que represente a proporção da imagem que ela contém. Em nosso exemplo, a primeira imagem tem 960px de largura por 640px altura, então calculemos a proporção.

640 (altura) é dois terços de 960 (largura), significando que para cada 2 unidade de altura, teremos 3 de largura. Proporções, tipicamente, são representadas por largura:altura, como em 16:9. Nossa proporção do exemplo é 3:2.

Para representar essa proporção, daremos à div a classe ratio_3_2.

1
    <div class="ratio_3_2">
2
      <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" height="640" width="960">
3
    </div>

Adicione Estilo Padrão de Proporção de Tela

Agora, adicionaremos o CSS que fará isso funcionar.

Dentro da tag <style>, no cabeçalho do arquivo index.html, adicione esse código:

1
    div[class^="ratio_"]{
2
      position: relative;
3
      width: 100%;
4
    }

Esse seletor estilizará nossa classe ratio_3_2 mas, também, estilizará outras classes que começam com ratio_. Isso significa que podemos criar mais classes para outras proporções.

Dentro do estilo, garantimos que o recipiente sempre toma 100% da largura do elemento pai. Também usamos position: relative; já que isso posicionará absolutamente a imagem dentro dele—veremos o porque disso logo.

Dê Altura ao Container da Proporção de Tela

Agora, adicionaremos isso apenas à classe ratio_3_2:

1
    .ratio_3_2 {
2
      /*padding-top: calc( 100% * (2 / 3) );*/
3
      padding-top: 66.666667%;
4
    }

O padding-top é o que nos permite manter nossa div recipiente na proporção que queremos. Seja qual for a largura da div, esse espaçamento manterá a altura como 66.666667% do valor (dois terços), mantendo, assim, a proporção 3:2.

Para determinar o percentual colocado aqui, descubra o quanto, em precentual, a altura representa da largura. Pode-se fazer isso com esse cálculo:

100% * (altura / largura)

Para a proporção 3:2, temos: 100% * (2 / 3) = 66.666667%.

Pode-se calcular esse percentual para sua proporção antecipadamente ou, caso prefira, usando a função calc() do CSS como visto comentada no exemplo abaixo:

padding-top: calc( 100% * (2 / 3) );

Coloque a Imagem Dentro do Container da Proporção de Tela

Nosso container da proporção manterá as dimensões desejadas, independente da resolução da área de visualização. Então, tudo que precisamos é fazer a imagem preencher todo o container e, assim, herdar suas dimensões.

Faremos isso posicionando absolutamente qualquer imagem filha de classes ratio_, colocando-as no canto esquerdo superior e fazendo-a ter 100% da largura e da altura, assim:

1
    div[class^="ratio_"] > img {
2
      position: absolute;
3
      top: 0;
4
      left: 0;
5
      width: 100%;
6
      height: 100%;
7
    }

Veja sua primeira imagem e perceberá que ela toma a largura da área de visualiação, mas reduzirá ao reduzir a área e manterá sua proporção também.

Adicione Proporções de Telas Extras

Provavelmente, teremos imagens com vários tipos de proporções e queremos acomodá-las todas. No exemplo, as três primeiras imagens tem proporção 3:2 mas a quarta e quinta tem 16:9.

Para contabilizá-las, adicione uma nova classe, seguindo o padrão do nome da proporção, ratio_16_9, com padding-top correspondente:

1
    .ratio_16_9 {
2
      /*padding-top: calc( 100% * (9 / 16) );*/
3
      padding-top: 56.25%;
4
    }

Vá em frente e adicione a proporção aos containers div nas outras imagens, usando as classes apropriadas para cada imagem. Também podemos remover os atributos height e width das nossas imagens, uma vez que serão sobrescritos pelo CSS.

1
    <div class="ratio_3_2">
2
      <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture">
3
    </div>
4
5
    <div class="ratio_3_2">
6
      <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach">
7
    </div>
8
9
    <div class="ratio_3_2">
10
      <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear">
11
    </div>
12
13
    <div class="ratio_16_9">
14
      <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky">
15
    </div>
16
17
    <div class="ratio_16_9">
18
      <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike">
19
    </div>

Recarregue o navegador e redimensione a área de visualização: é possível ver todas as imagens responsivas e preservando a funcionalidade de carga sob demanda, sem refluxo.

6. Adicione o srcset

Layzr também suporta o atributo srcset. Em navegadores que suportam srcset, será dada preferência a ele ao invés dos data-normal e data-retina.

Ao invés de usar o atributo srcset direto, devemos prefixá-lo com data-, como os outros atributos que temos usado.

Atualize o código da primeira imagem para:

1
<img 
2
  data-normal="images/vulture.jpg"
3
  data-retina="images/vulture@2x.jpg"
4
  alt="Vulture"
5
  data-srcset="images/vulture_sml.jpg 320w, images/vulture_med.jpg 640w, images/vulture.jpg 960w">

Para vê-la em funcionamento no navegador, reduza a área de visualização até 320px de largura, recarregue e veja o painel de rede. A menor versão da imagem deveria ser carregada primeiro. E, ao aumentar o tamanho da área de visualização deveria ver as versões média e grande serem carregadas de acordo.

O diretório de imagens provida junto ao código fonte incluem versões pequena, média e grande de cada imagem. Atualizemos o código de todas para usar o atributo data-srset:

1
    <div class="ratio_3_2">
2
      <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" data-srcset="images/vulture_sml.jpg 320w, images/vulture_med.jpg 640w, images/vulture.jpg 960w">
3
    </div>
4
5
    <div class="ratio_3_2">
6
      <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach" data-srcset="images/beach_sml.jpg 320w, images/beach_med.jpg 640w, images/beach.jpg 960w">
7
    </div>
8
9
    <div class="ratio_3_2">
10
      <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear" data-srcset="images/bear_sml.jpg 320w, images/bear_med.jpg 640w, images/bear.jpg 960w">
11
    </div>
12
13
    <div class="ratio_16_9">
14
      <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky" data-srcset="images/sky_sml.jpg 320w, images/sky_med.jpg 640w, images/sky.jpg 960w">
15
    </div>
16
17
    <div class="ratio_16_9">
18
      <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike" data-srcset="images/bike_sml.jpg 320w, images/bike_med.jpg 640w, images/bike.jpg 960w">
19
    </div>

Adicione Animação de Carregamento

Estamos quase prontos, mas precisamos lapidar um pouco mais, adicionando uma animação de carregamento. Isso comunicará aos visitantes que partes do layout estão agindo como marcadores e as imagens verdadeiras estão baixando.

Usaremos uma animação pura em CSS, uma versão um pouo modificada de uma pen de Alan Shortis: https://codepen.io/alanshortis/pen/eJLVXr

Para evitar qualquer código extra, adicionaremos nossa animação no pseudo-elemento :after anexado a cada recipiente de proporção. Adicionemos o código a seguir ao CSS:

1
    div[class^="ratio_"]:after {
2
      content: '';
3
      display: block;
4
      width: 3rem;
5
      height: 3rem;
6
      border-radius: 50%;
7
      border: .5rem double #444;
8
      border-left: .5rem double white;
9
      position: absolute;
10
      top: calc(50% - 2rem);
11
      left: calc(50% - 2rem);
12
      animation: spin 0.75s infinite linear;
13
    }
14
    @keyframes spin {
15
      0% {
16
        transform: rotate(0deg);
17
      }
18
      100% {
19
        transform: rotate(360deg);
20
      }
21
    }

O código acima cria a forma de um pequeno círculo de carregamento, centralizado e girando 360 graus a cada 0.75s.

Também adicionaremos uma plano de fundo cinza escuro ao container da proporção para que seja bem distinto do restante do layout. Adicione background-color: #333; assim:

1
    div[class^="ratio_"]{
2
      position: relative;
3
      width: 100%;
4
      background-color: #333;
5
    }

Por fim, precisamos garantir que a animação não se posiciona acima das imagens finais. Para tanto, adicione a z-index: 1; a nossas imagens, colocando-as uma camada acima das animações.

1
    div[class^="ratio_"] > img {
2
      position: absolute;
3
      top: 0;
4
      left: 0;
5
      width: 100%;
6
      height: 100%;
7
      z-index: 1;
8
    }

Atualizemos a página agora e deveremos ver as animações de carregamento em ação.

O Código Final

Com todo o código usado no artigo, nosso código deve ser assim:

1
<!doctype html>
2
<html lang="en">
3
  <head>
4
    <meta charset="utf-8">
5
    <title>Layzr.js Lazy Loading</title>
6
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
7
    <style>
8
    body {
9
      margin: 0;
10
    }
11
    img {
12
      display: block;
13
      margin: 0 auto;
14
    }
15
16
    div[class^="ratio_"]{
17
      position: relative;
18
      width: 100%;
19
      background-color: #333;
20
    }
21
    .ratio_3_2 {
22
      /*padding-top: calc( 100% * (2 / 3) );*/
23
      padding-top: 66.666667%;
24
    }
25
    .ratio_16_9 {
26
      /*padding-top: calc( 100% * (9 / 16) );*/
27
      padding-top: 56.25%;
28
    }
29
30
    div[class^="ratio_"] > img {
31
      position: absolute;
32
      top: 0;
33
      left: 0;
34
      width: 100%;
35
      height: 100%;
36
      z-index: 1;
37
    }
38
39
    div[class^="ratio_"]:after {
40
      content: '';
41
      display: block;
42
      width: 3rem;
43
      height: 3rem;
44
      border-radius: 50%;
45
      border: .5rem double #444;
46
      border-left: .5rem double white;
47
      position: absolute;
48
      top: calc(50% - 2rem);
49
      left: calc(50% - 2rem);
50
      animation: spin 0.75s infinite linear;
51
    }
52
    @keyframes spin {
53
      0% {
54
        transform: rotate(0deg);
55
      }
56
      100% {
57
        transform: rotate(360deg);
58
      }
59
    }
60
    </style>
61
  </head>
62
  <body>
63
    <h1>Welcome to the lazy loaded web</h1>
64
65
    <div class="ratio_3_2">
66
      <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" data-srcset="images/vulture_sml.jpg 320w, images/vulture_med.jpg 640w, images/vulture.jpg 960w">
67
    </div>
68
69
    <div class="ratio_3_2">
70
      <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach" data-srcset="images/beach_sml.jpg 320w, images/beach_med.jpg 640w, images/beach.jpg 960w">
71
    </div>
72
73
    <div class="ratio_3_2">
74
      <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear" data-srcset="images/bear_sml.jpg 320w, images/bear_med.jpg 640w, images/bear.jpg 960w">
75
    </div>
76
77
    <div class="ratio_16_9">
78
      <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky" data-srcset="images/sky_sml.jpg 320w, images/sky_med.jpg 640w, images/sky.jpg 960w">
79
    </div>
80
81
    <div class="ratio_16_9">
82
      <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike" data-srcset="images/bike_sml.jpg 320w, images/bike_med.jpg 640w, images/bike.jpg 960w">
83
    </div>
84
85
    <script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.2/layzr.min.js"></script>
86
    <script>
87
    const instance = Layzr()
88
89
    document.addEventListener('DOMContentLoaded', function(event){
90
      instance.update().check().handlers(true)
91
    })
92
    </script>
93
94
  </body>
95
</html>

Resumindo

Acabaos de implementar, à mão, carregamento sob demanda, o mais próximo possível dos recurso do AMP.

Há algumas coisas que o AMp faz automaticamente, como preservação da proporção das imagens responsivas, mas, por outro lado, fazer tudo manualmente dá controle extra, como especificar o limte de carregamento das imagens.

Esperamos que passar por esse processo tenha ajudado na escolha de qual abordagem usar.

Tiny technicians with network cablesTiny technicians with network cablesTiny technicians with network cables
Pequenos técnicos com cabos de rede, Kirill_M/Photodune

Obrigado ao Michael Cavalea por um excelente script! Para aprender mais sobre o Layzr.js, visite: https://github.com/callmecavs/layzr.js.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Photo & Video tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.