As barras de rolagem personalizadas são extremamente raras. Isso se deve principalmente ao fato de que as barras de rolagem são um dos bits restantes na Web que são praticamente não estilizável (Estou olhando para você, seletor de datas). Você pode usar o JavaScript para criar o seu próprio, mas isso é caro, barato a fidelidade e podem gerar lentidão. Neste artigo, vamos usar matrizes CSS não convencionais para criar um botão de rolagem personalizado que não JavaScript durante a rolagem, apenas um pouco de código de configuração.
Texto longo, leia o resumo
Você não se importa com as pequenas coisas? Você só quer conferir Demonstração do gato Nyan e acessar a biblioteca? Você pode encontrar o código de demonstração na nossa Repositório do GitHub.
LAM;WRA (longo e matemático; vai ser lido mesmo assim)
Há algum tempo, construímos um botão de rolagem de paralaxe (você leu aquele artigo? É muito bom e vale a pena! Movendo os elementos de volta usando o CSS 3D transformações, os elementos se moveram mais lentamente do que a velocidade real de rolagem.
Recapitulação
Vamos começar recapitulando como o botão de rolagem de paralaxe funcionava.
Como mostrado na animação, alcançamos o efeito paralaxe ao empurrar os elementos "para trás" no espaço 3D, ao longo do eixo Z. Rolar um documento é efetivamente uma tradução ao longo do eixo Y. Então, se rolarmos para baixo em 100 px, cada será traduzido para cima em 100 px. Isso se aplica a todos os elementos, mesmo os que estão “mais atrás”. Mas porque eles estão mais longe câmera, o movimento observado na tela será inferior a 100 px, produzindo o efeito de paralaxe desejado.
É claro que mover um elemento de volta para o espaço também o fará parecer menor, corrigindo, redimensionando o elemento novamente. Descobrimos os cálculos exatos quando criamos rolador de paralaxe, para não repetir todos os detalhes.
Etapa 0: o que queremos fazer?
Barras de rolagem. É isso que vamos criar. Mas você já pensou sobre o que ela faz? Eu com certeza não fiz isso. As barras de rolagem indicam quanto do conteúdo disponível está visível no momento e quanto progresso você, como o leitor, fez. Se você rolar para baixo, o mesmo acontece com a barra de rolagem indicam que você está progredindo para o final. Se todo o conteúdo se encaixar na janela de visualização, a barra de rolagem geralmente fica escondida. Se o conteúdo tiver o dobro da altura da janela de visualização, o a barra de rolagem preenche 1⁄2 da altura da janela de visualização. Conteúdo que vale três vezes a altura a janela de visualização dimensiona a barra de rolagem para 1⁄3 da janela etc. Você vê o padrão. Em vez de rolar, você também pode clicar e arrastar a barra de rolagem para passar o site com mais rapidez. É uma quantidade surpreendente de comportamento para uma um elemento desse tipo. Vamos lutar uma batalha de cada vez.
Etapa 1: ao contrário
Certo, podemos fazer com que os elementos se movam mais lentamente do que a velocidade de rolagem com CSS 3D. conforme descrito no artigo sobre rolagem de paralaxe. Também podemos reverter a direção? Acontece que sim, e essa é a nossa maneira de construir uma uma barra de rolagem personalizada perfeita. Para entender como isso funciona, precisamos abordar um alguns fundamentos de CSS 3D primeiro.
Para ter qualquer tipo de projeção de perspectiva no sentido matemático, você provavelmente acabar usando coordenadas homogêneas. Não vou entrar em detalhes sobre o que são e por que funcionam, mas você pode pensar com base em coordenadas 3D com uma quarta coordenada adicional, chamada w. Isso coordenada deve ser 1, exceto se você quiser distorção de perspectiva. Qa não precisa se preocupar com os detalhes de w, pois não usaremos nenhum diferente de 1. Portanto, a partir de agora, todos os pontos são vetores de quatro dimensões. [x, y, z, w=1] e, consequentemente, as matrizes precisam também seja 4x4.
Uma ocasião em que você pode ver que o CSS usa coordenadas homogêneas sob o elemento
Capô é quando você define suas próprias matrizes 4x4 em uma propriedade de transformação usando o
função matrix3d()
. matrix3d
usa 16 argumentos (porque a matriz é
4 x 4), especificando uma coluna após a outra. Então, podemos usar essa função para
especificar manualmente rotações, conversões etc. Mas isso também nos permite fazer
está mexendo na coordenada w!
Antes de usarmos matrix3d()
, precisamos de um contexto 3D, porque sem uma
no contexto 3D, não haveria distorção de perspectiva e não seria necessário
coordenadas homogêneas. Para criar um contexto 3D, precisamos de um contêiner com
perspective
e alguns elementos que podemos transformar na nova
criou espaço 3D. Para
exemplo:
![Um código CSS que distorce um div usando o
atributo de perspectiva.](https://developer.chrome.com/static/docs/css-ui/custom-scrollbar/image/a-piece-css-code-distor-1ed892aadfd7c.png?authuser=19&hl=pt-br)
Os elementos dentro de um contêiner de perspectiva são processados pelo mecanismo CSS da seguinte forma:
- Transformar cada canto (vértice) de um elemento em coordenadas homogêneas
[x,y,z,w]
, em relação ao contêiner de perspectiva. - Aplique todas as transformações do elemento como matrizes da direita para a esquerda.
- Se o elemento de perspectiva for rolável, aplique uma matriz de rolagem.
- Aplique a matriz de perspectiva.
A matriz de rolagem é uma translação ao longo do eixo Y. Se rolarmos para baixo 400 px, todos os elementos precisam ser movidos por 400 px. A matriz de perspectiva é uma matriz que "puxa" os pontos mais próximos do ponto de fuga em 3D espaço em que estão. Isso alcança os dois efeitos de fazer as coisas parecerem menores quando estão mais distantes e também faz com que “avancem mais devagar” durante a tradução. Portanto, se um elemento for empurrado para trás, uma translação de 400px fará com que o elemento para mover somente 300px na tela.
Se quiser saber todos os detalhes, leia a spec sobre a política mas, para este artigo, simplificamos a algoritmo acima.
Nossa caixa está dentro de um contêiner de perspectiva com valor p para perspective
.
e vamos supor que o contêiner é rolável e é rolado para baixo
n pixels.
A primeira matriz é a matriz de perspectiva, a segunda é a matriz de rolagem, matricial. Recapitulando: a função da matriz de rolagem é fazer com que um elemento se mova para cima quando rolam para baixo, daí o sinal negativo.
No entanto, para a barra de rolagem, queremos o oposto, ou seja, o elemento que
mover para baixo quando estivermos rolando para baixo. Aqui está onde podemos usar um truque:
Invertendo a coordenada w dos cantos do box. Se a coordenada w for
-1, todas as traduções entrarão em vigor na direção oposta. Então, como fazemos
isso? O mecanismo CSS cuida de converter os cantos da caixa em
coordenadas homogêneas e define w para 1. É hora de matrix3d()
brilhar!
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
);
}
Essa matriz não fará nada mais que negar w. Portanto, quando o mecanismo CSS tem
transformou cada canto em um vetor no formato [x,y,z,1]
, a matriz
e converteremos em [x,y,z,-1]
.
Listei uma etapa intermediária para mostrar o efeito da transformação do elemento matricial. Se não estiver confortável com matemática matricial, tudo bem. Eureka é que, na última linha, acabamos adicionando o deslocamento de rolagem n ao nosso valor y coordenada em vez de subtraí-la. O elemento será convertido para baixo se rolarmos para baixo.
No entanto, se colocarmos essa matriz em nosso exemplo, o elemento não será exibido. Isso ocorre porque a especificação do CSS exige que vértice com w < O valor 0 impede que o elemento seja renderizado. E como nosso z coordenada é 0 no momento e p é 1, w será -1.
Felizmente, podemos escolher o valor de z. Para garantir que o resultado seja w=1, precisamos para definir z = -2.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
Ei, nossos a caixa está de volta!
Etapa 2: faça a mudança
Agora nossa caixa está lá e tem a mesma aparência que teria sem nenhuma de dados. No momento, não é possível rolar o contêiner da perspectiva. Portanto, não podemos mas sabemos que nosso elemento seguirá em outra direção quando rolou. Então, vamos fazer o contêiner rolar, certo? Basta adicionar um elemento espaçador que ocupa espaço:
<div class="container">
<div class="box"></div>
<span class="spacer"></span>
</div>
<style>
/* … all the styles from the previous example … */
.container {
overflow: scroll;
}
.spacer {
display: block;
height: 500px;
}
</style>
E agora role a caixa para baixo. A caixa vermelha se move para baixo.
Etapa 3: dar um tamanho
Temos um elemento que se move para baixo quando a página rola para baixo. Essa é a parte difícil um pouco fora do caminho, na verdade. Agora precisamos estilizá-la para que se pareça com uma barra de rolagem e torná-lo um pouco mais interativo.
Uma barra de rolagem geralmente consiste em um "dedo" e uma "faixa", mas a faixa não é está sempre visível. A altura do polegar é diretamente proporcional a quanto o conteúdo fica visível.
<script>
const scroller = document.querySelector('.container');
const thumb = document.querySelector('.box');
const scrollerHeight = scroller.getBoundingClientRect().height;
thumb.style.height = /* ??? */;
</script>
scrollerHeight
é a altura do elemento rolável, enquanto
scroller.scrollHeight
é a altura total do conteúdo rolável.
scrollerHeight/scroller.scrollHeight
é a fração do conteúdo que é
visíveis. A proporção do espaço vertical das capas de polegar deve ser igual ao
proporção de conteúdo visível:
<script>
// …
thumb.style.height =
scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
// Accommodate for native scrollbars
thumb.style.right =
(scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>
O tamanho do polegar é que está bonito, mas está se movendo muito rápido. É aqui que podemos pegar nossa técnica botão de rolagem de paralaxe. Se movermos o elemento para trás, ele se moverá mais devagar enquanto rolagem. Podemos corrigir o tamanho aumentando-o. Mas quanto devemos empurrar exatamente de volta? Vamos fazer uma parte, você adivinhou, matemática! Esta é a última vez, eu promessa.
A informação essencial é que queremos que a borda inferior do polegar
alinhada com a borda inferior do elemento rolável quando ele é totalmente rolado
para baixo. Em outras palavras: se rolarmos a tela
scroller.scrollHeight - scroller.height
pixels, queremos que o polegar fique
traduzido por scroller.height - thumb.height
. Para cada pixel de rolagem,
queremos que o polegar mova uma fração do pixel:
Esse é o nosso fator de escalonamento. Agora precisamos converter o fator de escalonamento
translação ao longo do eixo z, o que já fizemos na rolagem de paralaxe.
artigo. De acordo com as
seção relevante na especificação:
O fator de escalonamento é igual a p/(p - z). Podemos resolver esta equação de z a
para descobrir o quanto precisamos para traduzir nosso polegar ao longo do eixo z. Mas mantenha
em mente que, devido a nossos manobras das coordenadas w, precisamos traduzir uma
mais -2px
ao longo de z. As transformações de um elemento são aplicadas
da direita para a esquerda, o que significa que todas as traduções antes de nossa matriz especial não
ser invertidas, todas as traduções depois da nossa matriz especial serão! Vamos
codifique isso!
<script>
// ... code from above...
const factor =
(scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
thumb.style.transform = `
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
scale(${1/factor})
translateZ(${1 - 1/factor}px)
translateZ(-2px)
`;
</script>
Temos um scrollbar. E é apenas um elemento DOM que podemos estilizar como quiser. Algo que em termos de acessibilidade é fazer o polegar responder clicar e arrastar, já que muitos usuários estão acostumados a interagir com uma barra de rolagem dessa forma. Para não tornar esta postagem do blog ainda mais longa, não vou explicar os detalhes daquela parte. Dê uma olhada no código da biblioteca para saber como isso é feito.
E o iOS?
Ah, meu velho amigo iOS Safari. Assim como na rolagem paralaxe, nos deparamos com uma
problema aqui. Como estamos rolando em um elemento, precisamos especificar
-webkit-overflow-scrolling: touch
, mas isso causa o achatamento 3D e toda a
o efeito de rolagem para de funcionar. Resolvemos esse problema com o botão de rolagem de paralaxe
detectando o Safari do iOS e contando com o position: sticky
como solução alternativa, e
vamos fazer exatamente o mesmo aqui. Dê uma olhada no
artigo sobre paralaxe
para refrescar sua memória.
E a barra de rolagem do navegador?
Em alguns sistemas, será necessário usar uma barra de rolagem nativa e permanente.
Historicamente, a barra de rolagem não pode ser ocultada (exceto com uma
pseudoseletor não padrão).
Então, para ocultá-lo, temos que recorrer a alguns hackers (sem matemática). Concluímos
elemento de rolagem em um contêiner com overflow-x: hidden
e tornar o
elemento de rolagem mais largo que o contêiner. A barra de rolagem nativa do navegador é
agora fora de vista.
Fin
Juntando tudo, podemos criar um frame personalizado perfeito como a de nosso Demonstração do gato Nyan (em inglês).
Se você não consegue ver o gato Nyan, isso significa que um bug que encontramos e registramos ao criar esta demonstração (clique no ícone de polegar para que o gato Nyan apareça). O Chrome é muito bom em evitar trabalhos desnecessários como pintar ou animar coisas que estão fora da tela. A má notícia é que nossos manobras matriciais fazem o Chrome pensar que o gif do gato Nyan está, na verdade, fora da tela. Esperamos que o problema seja corrigido em breve.
Aí está. Foi muito trabalho. Parabéns por ler o livro inteiro coisa. Estes são alguns para fazer isso funcionar e provavelmente raramente vai valer o esforço, exceto quando uma barra de rolagem personalizada é uma parte essencial da experiência. Mas é bom saber que isso é possível, né? O fato de ser tão difícil fazer barra de rolagem personalizada mostra que há trabalho a ser feito no lado do CSS. Mas não se preocupe! No futuro, de Houdini AnimationWorklet vai facilitar muito mais fáceis de aplicar efeitos de rolagem perfeitos, como esse.