Como Criar um Indicador de Rolagem de Página com jQuery e SVG
Portuguese (Português) translation by Erick Patrick (you can also view the original English article)
Hoje, veremos algumas técnicas que podemos usar para mostrar o progresso da rolagem de páginas que o usuário esteja lendo. Essa técnica tem sido usada por um crescente número de sites e por uma boa razão: ela provê um retorno do tempo a ser investido para consumir uma página em particular. De acordo com que o usuário rola a página, eles são apresentados a um progresso atual em diferentes formatos.



Hoje, mostrarei duas técnicas específicas que podem ser usadas para mostrar essa progressão, dando-lhe a oportunidade de criar o seu próprio indicador usando esse conjunto de ferramentas. Comecemos!
Preparando o Documento
Primeiro, prepararemos um documento que funcionará como nossa página. Nós usaremos o normalize.css
e a jQuery, assim como uma fonte lá do Google font. Seu arquivo HTML deve parecer mais ou menos com isso:
1 |
<!doctype html>
|
2 |
<html>
|
3 |
<head>
|
4 |
<title>Animação do Indicador de Progresso</title> |
5 |
<link rel="stylesheet" href="css/normalize.css"> |
6 |
<link rel="stylesheet" href="css/style.css"> |
7 |
</head>
|
8 |
<body>
|
9 |
<!-- Conteúdo qualquer vem aqui -->
|
10 |
<script src="js/jquery.min.js"></script> |
11 |
<script src="js/script.js"></script> |
12 |
</body>
|
13 |
</html>
|
O próximo passo é adicionar o conteúdo falso da nossa página:
1 |
<main>
|
2 |
<article>
|
3 |
<header>
|
4 |
<h1>
|
5 |
<div class="container"> |
6 |
Como Indicar o Progresso Enquanto Rolamos uma Página? |
7 |
</div>
|
8 |
</h1>
|
9 |
</header>
|
10 |
<div class="article-content"> |
11 |
<h2 class="lead-in"> |
12 |
<div class="container"> |
13 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. |
14 |
</div>
|
15 |
</h2>
|
16 |
<div class="container"> |
17 |
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p> |
18 |
<!-- adicione algum texto extra a partir daqui, se quiser -->
|
19 |
</div>
|
20 |
</div>
|
21 |
</article>
|
22 |
<footer>
|
23 |
<h3 class="read-next"><small>Próximo:</small><br>Como Implementar um Foobar?</h3> |
24 |
</footer>
|
25 |
</main>
|
Isso nos dá conteúdo suficiente para testar o comportamento da rolagem.
Estilização Básica
Usaremos uma estilização básica para fazer nossa página aparentar ser mais atrativa.
1 |
@import url(http://fonts.googleapis.com/css?family=Domine:400,700); |
2 |
body { |
3 |
font-size: 16px; |
4 |
}
|
5 |
h1, |
6 |
h2, |
7 |
h3, |
8 |
h4, |
9 |
h5, |
10 |
h6 { |
11 |
font-family: "Domine", sans-serif; |
12 |
}
|
13 |
|
14 |
h1 { |
15 |
font-size: 3.5em; |
16 |
}
|
17 |
|
18 |
.lead-in { |
19 |
color: #fff; |
20 |
font-weight: 400; |
21 |
padding: 60px 0; |
22 |
background-color: #0082FF; |
23 |
}
|
24 |
|
25 |
article header { |
26 |
border-top: 3px solid #777; |
27 |
padding: 80px 0; |
28 |
}
|
29 |
|
30 |
.article-content { |
31 |
font-size: 1em; |
32 |
font-weight: 100; |
33 |
line-height: 2.4em; |
34 |
}
|
35 |
|
36 |
p { |
37 |
margin: 4em 0; |
38 |
}
|
39 |
|
40 |
.container { |
41 |
width: 700px; |
42 |
margin: 0 auto; |
43 |
}
|
44 |
|
45 |
|
46 |
footer { |
47 |
text-align: center; |
48 |
background-color: #666; |
49 |
color: #fff; |
50 |
padding: 40px 0; |
51 |
margin-top: 60px; |
52 |
}
|
53 |
|
54 |
.read-next { |
55 |
font-size: 2em; |
56 |
}
|
Cálculo da Posição da Barra de Rolagem
Para calcular a posição da barra de rolagem, precisamos entender o conceito do que estamos rastreando. Uma vez que o JavaScript só é capaz de rastrear o valor rolado começando do topo da página, precisaremos rastrear a partir do valor 0 (bem no topo, sem qualquer rolagem), até o valor que seja o final da rolagem. O valor final será igual ao tamanho total do documento menos o tamanho da tela em si (uma vez que que o documento será rolado até que o final dele alcance o final da janela).



Usaremos o código JavaScript abaixo para calcular a posição da barra.
1 |
(function(){ |
2 |
var $w = $(window); |
3 |
var wh = $w.height(); |
4 |
var h = $('body').height(); |
5 |
var sHeight = h - wh; |
6 |
$w.on('scroll', function(){ |
7 |
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight)); |
8 |
});
|
9 |
|
10 |
}());
|
O código acima obtem o valor da altura da janela e do corpo da página, e quando o usuário rolar a página, usaremos esses valores para configurar o valor da variável perc
(abreviação para percentage). Também utilizamos Math.min
e Math.max
para limitar os valores ao intervalo 0-100.
Com o cálculo desse percentual, podemos manipular o indicador de progressão.
Indicador Circular
O primeiro indicador que falaremos será o de círculo em SVG. Utilizaremos as propriedades stroke-dasharray
e stroke-dashoffset
do SVG para mostrar a progressão. Primeiro, adicionemos o indicador ao documento.
1 |
<div class="progress-indicator"> |
2 |
<svg>
|
3 |
<g>
|
4 |
<circle cx="0" cy="0" r="20" class="animated-circle" transform="translate(50,50) rotate(-90)" /> |
5 |
</g>
|
6 |
<g>
|
7 |
<circle cx="0" cy="0" r="38" transform="translate(50,50)" /> |
8 |
</g>
|
9 |
</svg>
|
10 |
<div class="progress-count"></div> |
11 |
</div>
|
Esse trecho nos mostra dois círculos em SVG, assim como uma div
para mostrar o nosso percentual. É preciso adicionar estilos a esses elementos também. Depois disso, explicaremos como esses círculos serão posicionados e animados.
1 |
.progress-indicator { |
2 |
position: fixed; |
3 |
top: 30px; |
4 |
right: 30px; |
5 |
width: 100px; |
6 |
height: 100px; |
7 |
}
|
8 |
.progress-count { |
9 |
position: absolute; |
10 |
top: 0; |
11 |
left: 0; |
12 |
width: 100%; |
13 |
height: 100%; |
14 |
text-align: center; |
15 |
line-height: 100px; |
16 |
color: #0082FF; |
17 |
}
|
18 |
|
19 |
svg { |
20 |
position: absolute; |
21 |
}
|
22 |
circle { |
23 |
fill: rgba(255,255,255,0.9); |
24 |
}
|
25 |
|
26 |
svg .animated-circle { |
27 |
fill: transparent; |
28 |
stroke-width: 40px; |
29 |
stroke: #0A74DA; |
30 |
stroke-dasharray: 126; |
31 |
stroke-dashoffset: 126; |
32 |
}
|
Esses estilos preparam os círculos para a animação. Nosso indicador de progressão sempre deve estar visível, assim, o fixamos com a classe .progress-indicator
, usando regras de posicionamento e tamanho. Também centralizaremos a contagem da progressão, tanto vertical quanto horizontalmente, dentro da div
.
Os círculos são posicionados no centro usando a propriedade transform
, diretamente, nos elementos SVG. Começamos o centro do nosso círculo com a transform
. Usamos uma técnica que permite aplicarmos uma rotação a partir do centro de nossos círculos, de modo que possamos começar a animação a partir do topo do círculo (ao invés do lado direito dele). No SVG, transform
é aplicado a partir do topo esquerdo de um elemento. É por isso que precisamos centralizar nossos círculos na posição 0, 0
e mover o centro do próprio círculo para o centro da área do SVG, usando translate(50, 50)
.
Usando stroke-dasharray
e stroke-dashoffset
As propriedades stroke-dasharray
e stroke-dashoffset
permitem aplicar bordas a um SVG. A stroke-dasharray
define as partes visíveis da borda. A stroke-dashoffset
move o começo da borda. Esses atributos combinados nos ajudam a criar o processo de animação.
Atualizando stroke-dasharray
Com Base na Rolagem
Agora, adicionaremos uma função para atualizar a stroke-dasharray
baseado na rolagem, usando a progressão percentual apresentada anteriormente.
1 |
(function(){ |
2 |
var $w = $(window); |
3 |
var $circ = $('.animated-circle'); |
4 |
var $progCount = $('.progress-count'); |
5 |
var wh = $w.height(); |
6 |
var h = $('body').height(); |
7 |
var sHeight = h - wh; |
8 |
$w.on('scroll', function(){ |
9 |
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight)); |
10 |
updateProgress(perc); |
11 |
});
|
12 |
|
13 |
function updateProgress(perc){ |
14 |
var circle_offset = 126 * perc; |
15 |
$circ.css({ |
16 |
"stroke-dashoffset" : 126 - circle_offset |
17 |
});
|
18 |
$progCount.html(Math.round(perc * 100) + "%"); |
19 |
}
|
20 |
|
21 |
}());
|
O deslocamento que combina com nosso círculo inicia em 126. É importante notar que isso não funciona para todos os círculos, uma vez que 126 é aproximadamente a circunferência de um círculo com 20 de raio. Para calcular o valor de stroke-dashoffset
para um dado círculo, multiplique seu raio por 2PI. Em nosso caso, o deslocamento exato seria 20 * 2PI = 125.66370614359172
.
Variação Horizontal da Progressão
Em nosso próximo exemplo, faremos uma simples barra horizontal, fixada ao topo da janela. Para conseguir esse efeito, usaremos uma div
vazia para indicar a progressão.
1 |
<div class="progress-indicator-2"></div> |
Nota: Nós adicionamos o sufixo "-2" para permitir a inclusão desse exemplo no mesmo arquivo CSS.
Agora, adicionaremos a estilização desse elemento.
1 |
.progress-indicator-2 { |
2 |
position: fixed; |
3 |
top: 0; |
4 |
left: 0; |
5 |
height: 3px; |
6 |
background-color: #0A74DA; |
7 |
}
|
finalmente, configuraremos a largura da barra de progresso a partir da barra de rolagem.
1 |
var $prog2 = $('.progress-indicator-2'); |
2 |
function updateProgress(perc){ |
3 |
$prog2.css({width : perc*100 + '%'}); |
4 |
}
|
O nosso JavaScript finalizado e completo deve ficar mais ou menos assim:
1 |
(function(){ |
2 |
var $w = $(window); |
3 |
var $circ = $('.animated-circle'); |
4 |
var $progCount = $('.progress-count'); |
5 |
var $prog2 = $('.progress-indicator-2'); |
6 |
var wh = $w.height(); |
7 |
var h = $('body').height(); |
8 |
var sHeight = h - wh; |
9 |
$w.on('scroll', function(){ |
10 |
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight)); |
11 |
updateProgress(perc); |
12 |
});
|
13 |
|
14 |
function updateProgress(perc){ |
15 |
var circle_offset = 126 * perc; |
16 |
$circ.css({ |
17 |
"stroke-dashoffset" : 126 - circle_offset |
18 |
});
|
19 |
$progCount.html(Math.round(perc * 100) + "%"); |
20 |
|
21 |
$prog2.css({width : perc*100 + '%'}); |
22 |
}
|
23 |
|
24 |
}());
|
Outras Ideias Para Barras de Progressão
Esse artigo foi pensado para mostrar o básico a você, de forma que se inspire e crie suas próprias soluções para indicação de progressão. Outras ideais para barras de progresso podem incluir algo mais descritivo ou termos humanizados, como "quase lá" ou "apenas começando". Algumas implementações (como a da ia.net apresentada anteriormente) usam uma estimativa do tempo de leitura de um artigo. Esse tempo pode ser estimado usando um código parecido com o do trecho abaixo:
1 |
var wordsPerMin = 300; // baseado nesse artigo: http://www.forbes.com/sites/brettnelson/2012/06/04/do-you-read-fast-enough-to-be-successful/ |
2 |
var wordsArray = $(".article-content").text().split(" "); |
3 |
var wordCount = wordsArray.length; |
4 |
var minCount = Math.round(wordCount / wordsPerMin); |
Assim, você usaria a variável minCount
junto a perc
para apresentar ao leitor o tempo que falta para terminar de ler o artigo. Eis uma implementação bem básica desse conceito.
1 |
function updateProgress(perc){ |
2 |
var minutesCompleted = Math.round(perc * minCount); |
3 |
var remaining = minCount - minutesCompleted; |
4 |
if (remaining){ |
5 |
$(".progress-indicator").show().html(remaining + " minutes remaining"); |
6 |
} else { |
7 |
$(".progress-indicator").hide(); |
8 |
}
|
9 |
}
|
Um Último Ponto: Telas de Tamanhos Adaptáveis
Para garantir que nosso indicador funciona como devido, devemos garantir que nossos cálculos estão corretos a qualquer momento. Para que isso aconteça, precisamos recalcular as alturas e atualizar o indicador de progresso quando o usuário redimensionar a janela. Eis uma adaptação do JavaScript para tornar isso possível:
1 |
(function(){ |
2 |
var $w = $(window); |
3 |
var $circ = $('.animated-circle'); |
4 |
var $progCount = $('.progress-count'); |
5 |
var $prog2 = $('.progress-indicator-2'); |
6 |
|
7 |
var wh, h, sHeight; |
8 |
|
9 |
function setSizes(){ |
10 |
wh = $w.height(); |
11 |
h = $('body').height(); |
12 |
sHeight = h - wh; |
13 |
}
|
14 |
|
15 |
setSizes(); |
16 |
|
17 |
$w.on('scroll', function(){ |
18 |
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight)); |
19 |
updateProgress(perc); |
20 |
}).on('resize', function(){ |
21 |
setSizes(); |
22 |
$w.trigger('scroll'); |
23 |
});
|
24 |
|
25 |
function updateProgress(perc){ |
26 |
var circle_offset = 126 * perc; |
27 |
$circ.css({ |
28 |
"stroke-dashoffset" : 126 - circle_offset |
29 |
});
|
30 |
$progCount.html(Math.round(perc * 100) + "%"); |
31 |
|
32 |
$prog2.css({width : perc*100 + '%'}); |
33 |
}
|
34 |
|
35 |
}());
|
Nesse código, temos uma função que configura as variáveis necessárias para calcular a progressão em qualquer tamanho de tela, sendo executada toda vez que a tela é redimensionada. Também ativamos novamente a rolagem na janela para que nossa função updateProgress
seja executada.
Chegamos ao Fim!
Tendo dado os fundamentos para os mais variados tipos de indicadores, o que você pode fazer mais? Quais indicadores de progresso você tem visto? E quais indicadores são ruins para usabilidade? Compartilhe suas experiências nos comentários!
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!