Eu trabalho muito com layouts personalizados de tela inteira, praticamente diariamente. Normalmente, esses layouts implicam em uma quantidade significativa de interação e animação. Seja um cronograma de transições complexo acionado por tempo ou um conjunto de eventos orientado pelo usuário baseado em rolagem, na maioria dos casos, a IU requer mais do que apenas usar uma solução de plug-in pronta para uso com alguns ajustes e alterações . Por outro lado, vejo muitos Desenvolvedores de JavaScript tendem a buscar seu plugin JS favorito para facilitar seu trabalho, mesmo que a tarefa possa não precisar de todos os recursos que um determinado plugin oferece.
Aviso Legal: Usar um dos muitos plug-ins disponíveis por aí tem suas vantagens, é claro. Você terá uma variedade de opções que pode usar para ajustar as suas necessidades sem ter que fazer muita codificação. Além disso, a maioria dos autores de plug-ins otimiza seu código, torna-o compatível com navegadores e plataformas cruzadas e assim por diante. Mesmo assim, você obtém uma biblioteca em tamanho real incluída em seu projeto para, talvez, apenas um ou dois itens diferentes que ele fornece. Não estou dizendo que usar um plugin de terceiros de qualquer tipo é naturalmente uma coisa ruim, eu faço isso diariamente em meus projetos, apenas que geralmente é uma boa ideia pesar os prós e os contras de cada abordagem como ela é uma boa prática de codificação. Quando se trata de fazer suas próprias coisas dessa maneira, é necessário um pouco mais de conhecimento de codificação e experiência para saber o que você está procurando, mas no final, você deve obter um pedaço de código que faz uma coisa e uma coisa apenas da maneira você quer.
Este artigo tem como objetivo mostrar uma abordagem CSS / JS pura no desenvolvimento de um layout de controle deslizante acionado por rolagem em tela cheia com animação de conteúdo personalizado. Nesta abordagem em escala reduzida, cobrirei a estrutura HTML básica que você esperaria receber de um back-end CMS, CSS moderno ( SCSS ) técnicas de layout e codificação simples de JavaScript para total interatividade. Sendo o esqueleto, este conceito pode ser facilmente estendido para um plugin de maior escala e / ou usado em uma variedade de aplicações sem dependências em seu núcleo.
O design que vamos criar é uma vitrine de portfólio de arquiteto minimalista com imagens em destaque e títulos de cada projeto. O controle deslizante completo com animações terá a seguinte aparência:
Você pode conferir a demonstração Aqui , e você pode acessar meu Repositório Github para mais detalhes.
Então, aqui está o HTML básico com o qual trabalharemos:
hero-slider
Um div com o id de
é o nosso principal titular. No interior, o layout é dividido em seções: #64 Paradigm
Vamos nos concentrar na seção de apresentação de slides, já que esse é o nosso ponto de interesse neste artigo. Aqui temos duas partes - a Principal e ao . Main é o div que contém as imagens em destaque, enquanto aux contém os títulos das imagens. A estrutura de cada slide dentro desses dois suportes é bem básica. Aqui temos um slide de imagem dentro do suporte principal:
como aprender programação c
#hero-slider { position: relative; height: 100vh; display: flex; background: $dark-color; } #slideshow { position: relative; flex: 1 1 $main-width; display: flex; align-items: flex-end; padding: $offset; } #info { position: relative; flex: 1 1 $side-width; padding: $offset; background-color: #fff; }
O atributo de dados do índice é o que usaremos para manter o controle de onde estamos na apresentação de slides. A div máscara de abs que usaremos para criar um efeito de transição interessante e a div de imagem de slide contém a imagem em destaque específica. As imagens são renderizadas em linha como se viessem diretamente de um CMS e são definidas pelo usuário final.
Da mesma forma, o título desliza dentro do suporte auxiliar:
#slideshow { position: relative; flex: 1 1 $main-width; display: flex; align-items: flex-end; padding: $offset; } #slides-main { @extend %abs; &:after { content: ''; @extend %abs; background-color: rgba(0, 0, 0, .25); z-index: 100; } .slide-image { @extend %abs; background-position: center; background-size: cover; z-index: -1; } } #slides-aux { position: relative; top: 1.25rem; width: 100%; .slide-title { position: absolute; z-index: 300; font-size: 4vw; font-weight: 700; line-height: 1.3; @include outlined(#fff); } }
Cada título de slide é uma tag H2 com o atributo de dados correspondente e um link para levar à página única desse projeto.
O resto do nosso HTML também é bastante simples. Temos um logotipo no topo, informações estáticas que informam ao usuário em qual página ele está, alguma descrição e o indicador deslizante atual / total.
O código CSS fonte é escrito em SCSS , um pré-processador CSS que é então compilado em CSS regular que o navegador pode interpretar. O SCSS oferece a vantagem de usar variáveis, seleção aninhada, mixins e outras coisas legais, mas precisa ser compilado em CSS para que o navegador leia o código como deveria. Para o propósito deste tutorial, usei Scout-App para lidar com a compilação, pois eu queria ter as ferramentas no mínimo.
Usei o flexbox para lidar com o layout básico lado a lado. A ideia é ter o slideshow de um lado e a seção de informações do outro.
background-size: cover
Vamos mergulhar no posicionamento e, novamente, focar na seção da apresentação de slides:
%abs { position: absolute; top: 0; left: 0; height: 100%; width: 100%; }
Eu configurei o controle deslizante principal para estar absolutamente posicionado e as imagens de fundo esticam toda a área usando @mixin outlined($color: $dark-color, $size: 1px) { color: transparent; -webkit-text-stroke: $size $color; }
propriedade. Para fornecer mais contraste com os títulos dos slides, eu defini um pseudoelemento absoluto que atua como uma sobreposição. O controle deslizante auxiliar contendo os títulos dos slides está posicionado na parte inferior da tela e no topo das imagens.
Já que apenas um slide estará visível por vez, eu defino cada título como absoluto também, e tenho o tamanho do suporte calculado via JS para garantir que não haja cortes, mas mais sobre isso em uma de nossas próximas seções. Aqui você pode ver o uso de um recurso SCSS chamado extensão:
.slider-title-wrapper { position: absolute; top: $offset; left: calc(100% - #{$offset}); transform-origin: 0% 0%; transform: rotate(90deg); @include outlined; }
Como usei muito o posicionamento absoluto, coloquei esse CSS em um extensível para tê-lo facilmente disponível em vários seletores. Além disso, criei um mixin chamado “outlined” para fornecer uma abordagem DRY ao estilizar os títulos e o título do controle deslizante principal.
transform-origin
Quanto à parte estática deste layout, não tem nada de complexo, mas aqui você pode ver um método interessante ao posicionar o texto que deve estar no eixo Y em vez de seu fluxo normal:
ferramenta usada para visualizar todas as combinações possíveis
#logo:after { transform: scaleY(0); transform-origin: 50% 0; transition: transform .35s $easing; } .logo-text { display: block; transform: translate3d(120%, 0, 0); opacity: 0; transition: transform .8s .2s, opacity .5s .2s; } .current, .sep:before { opacity: 0; transition: opacity .4s 1.3s; } #info { transform: translate3d(100%, 0, 0); transition: transform 1s $easing .6s; } .line { transform-origin: 0% 0; transform: scaleX(0); transition: transform .7s $easing 1s; } .slider-title { overflow: hidden; >span { display: block; transform: translate3d(0, -100%, 0); transition: transform .5s 1.5s; } }
Gostaria de chamar sua atenção para o transform
propriedade pois achei muito subutilizada para este tipo de layout. A forma como esse elemento é posicionado é que sua âncora fica no canto superior esquerdo do elemento, definindo o ponto de rotação e fazendo com que o texto flua continuamente desse ponto para baixo sem problemas quando se trata de diferentes tamanhos de tela.
Vamos dar uma olhada em uma parte CSS mais interessante - a animação de carregamento inicial:
Normalmente, esse tipo de comportamento de animação sincronizado é obtido usando uma biblioteca - GSAP , por exemplo, é um dos melhores que existem, fornecendo excelentes recursos de renderização, é fácil de usar e tem a funcionalidade de linha de tempo que permite ao desenvolvedor encadear programaticamente as transições de elementos entre si.
No entanto, como este é um exemplo puro de CSS / JS, decidi usar o básico aqui. Portanto, cada elemento é definido em sua posição inicial por padrão - oculto por transformação ou opacidade e mostrado durante o carregamento do controle deslizante que é acionado por nosso JS. Todas as propriedades de transição são ajustadas manualmente para garantir um fluxo natural e interessante com cada transição continuando para outra, proporcionando uma experiência visual agradável.
transform
Se há algo que eu gostaria que você visse aqui, é o uso de heroSlider
propriedade. Ao mover um elemento HTML, seja uma transição ou animação, é aconselhável usar o utils
propriedade. Eu vejo muitas pessoas que tendem a usar a margem ou o preenchimento ou mesmo os deslocamentos - superior, esquerdo, etc., o que não produz resultados adequados quando se trata de renderização.
Para obter uma compreensão mais aprofundada de como usar CSS ao adicionar comportamento interativo, eu não poderia recomendar o artigo seguinte o suficiente.
É de Paul Lewis, um engenheiro do Chrome, e cobre praticamente tudo que se deve saber sobre renderização de pixels na web, seja CSS ou JS.
O arquivo JavaScript é dividido em duas funções distintas.
O init
que cuida de todas as funcionalidades de que precisamos aqui, e a resize
função onde eu adicionei várias funções de utilidade reutilizáveis. Eu comentei cada uma dessas funções utilitárias para fornecer contexto se você quiser reutilizá-las em seu projeto.
A função principal é codificada de forma que tenha dois ramos: init
e heroSlider
. Essas ramificações estão disponíveis por meio do retorno da função principal e são chamadas quando necessário. const slider = { hero: document.querySelector('#hero-slider'), main: document.querySelector('#slides-main'), aux: document.querySelector('#slides-aux'), current: document.querySelector('#slider-nav .current'), handle: null, idle: true, activeIndex: -1, interval: 3500 };
é a inicialização da função principal e é acionada no evento de carregamento da janela. Da mesma forma, a ramificação de redimensionamento é acionada no redimensionamento da janela. O único propósito da função de redimensionamento é recalcular o tamanho do controle deslizante do título no redimensionamento da janela, pois o tamanho da fonte do título pode variar.
causas da crise da dívida grega
No handle
função, forneci um objeto deslizante que contém todos os dados e seletores de que vamos precisar:
idle
Como uma observação lateral, essa abordagem pode ser facilmente adaptada se você estiver usando o React, por exemplo, já que pode armazenar os dados no estado ou usar os ganchos recém-adicionados. Para ficar no ponto, vamos ver o que cada um dos pares de valores-chave aqui representa:
activeIndex
será usada para iniciar e parar a funcionalidade de reprodução automática.interval
propriedade é um sinalizador que impedirá o usuário de forçar a rolagem enquanto o slide está em transição.setHeight(slider.aux, slider.aux.querySelectorAll('.slide-title')); loadingAnimation();
nos permitirá acompanhar o slide atualmente ativosetHeight
denota o intervalo de reprodução automática do controle deslizanteNa inicialização do controle deslizante, chamamos duas funções:
const loadingAnimation = function () { slider.hero.classList.add('ready'); slider.current.addEventListener('transitionend', start, { once: true }); }
O const start = function () { autoplay(true); wheelControl(); window.innerWidth <= 1024 && touchControl(); slider.aux.addEventListener('transitionend', loaded, { once: true }); }
função alcança uma função de utilidade para definir a altura do nosso controle deslizante aux com base no tamanho máximo do título. Dessa forma, garantimos que o tamanho adequado seja fornecido e nenhum título de slide seja cortado, mesmo quando o conteúdo cai em duas linhas.
A função loadingAnimation adiciona uma classe CSS ao elemento fornecendo as transições CSS de introdução:
loadingAnimation
Como nosso indicador deslizante é o último elemento na linha do tempo de transição CSS, esperamos que sua transição termine e invocamos a função de início. Ao fornecer um parâmetro adicional como um objeto, garantimos que ele seja acionado apenas uma vez.
Vamos dar uma olhada na função inicial:
const autoplay = function (initial) { slider.autoplay = true; slider.items = slider.hero.querySelectorAll('[data-index]'); slider.total = slider.items.length / 2; const loop = () => changeSlide('next'); initial && requestAnimationFrame(loop); slider.handle = utils().requestInterval(loop, slider.interval); }
Assim, quando o layout for concluído, sua transição inicial será acionada por slideChange
função e a função de início assume. Em seguida, ele aciona a funcionalidade de reprodução automática, ativa o controle de roda, determina se estamos em um dispositivo touch ou desktop e aguarda a primeira transição do slide de títulos para adicionar a classe CSS apropriada.
Um dos principais recursos desse layout é o recurso de reprodução automática. Vamos examinar a função correspondente:
requestAnimationFrame
Primeiro, definimos o sinalizador de reprodução automática como verdadeiro, indicando que o controle deslizante está no modo de reprodução automática. Este sinalizador é útil para determinar se a reprodução automática deve ser reativada após o usuário interagir com o controle deslizante. Em seguida, referenciamos todos os itens do controle deslizante (slides), pois alteraremos sua classe ativa e calcularemos o total de iterações que o controle deslizante terá, somando todos os itens e dividindo por dois, pois temos dois layouts de controle deslizante sincronizados (principal e aux) mas apenas um “controle deslizante” per se que muda os dois simultaneamente.
A parte mais interessante do código aqui é a função de loop. Ele invoca requestAnimationFrame
, fornecendo a direção do slide que examinaremos em um minuto, no entanto, a função de loop é chamada algumas vezes. Vamos ver por quê.
Se o argumento inicial for avaliado como verdadeiro, invocaremos a função de loop como um requestInterval
ligue de volta. Isso só acontece na primeira carga do controle deslizante, o que ativa a mudança imediata do slide. Usando setInterval
executamos o retorno de chamada fornecido pouco antes do próximo redesenho de quadro.
No entanto, como queremos continuar percorrendo os slides no modo de reprodução automática, usaremos uma chamada repetida dessa mesma função. Isso geralmente é obtido com setInterval. Mas, neste caso, usaremos uma das funções utilitárias– requestInterval
. Enquanto requestAnimationFrame
funcionaria muito bem, slider.handle
é um conceito avançado que se baseia em cancelAnimationFrame
e fornece uma abordagem com mais desempenho. Isso garante que a função seja disparada novamente apenas se a guia do navegador estiver ativa.
Mais sobre este conceito neste artigo incrível pode ser encontrado em Truques de CSS . Observe que atribuímos o valor de retorno desta função ao nosso slideChange
propriedade. Este ID exclusivo que a função retorna está disponível para nós e vamos usá-lo para cancelar a reprodução automática mais tarde, usando const changeSlide = function (direction) { slider.idle = false; slider.hero.classList.remove('prev', 'next'); if (direction == 'next') { slider.activeIndex = (slider.activeIndex + 1) % slider.total; slider.hero.classList.add('next'); } else { slider.activeIndex = (slider.activeIndex - 1 + slider.total) % slider.total; slider.hero.classList.add('prev'); } //reset classes utils().removeClasses(slider.items, ['prev', 'active']); //set prev const prevItems = [...slider.items] .filter(item => { let prevIndex; if (slider.hero.classList.contains('prev')) { prevIndex = slider.activeIndex == slider.total - 1 ? 0 : slider.activeIndex + 1; } else { prevIndex = slider.activeIndex == 0 ? slider.total - 1 : slider.activeIndex - 1; } return item.dataset.index == prevIndex; }); //set active const activeItems = [...slider.items] .filter(item => { return item.dataset.index == slider.activeIndex; }); utils().addClasses(prevItems, ['prev']); utils().addClasses(activeItems, ['active']); setCurrent(); const activeImageItem = slider.main.querySelector('.active'); activeImageItem.addEventListener('transitionend', waitForIdle, { once: true }); }
.
O wheelControl
função é a função principal em todo o conceito. Ele muda os slides, seja por reprodução automática ou por gatilho do usuário. Ele reconhece a direção do controle deslizante e fornece loop para que, ao chegar ao último slide, você possa continuar para o primeiro. Aqui está como eu codifiquei:
touchControl
A ideia é determinar o slide ativo com base em seu índice de dados obtido do HTML. Vamos abordar cada etapa:
setCurrent
ou waitForIdle
.const wheelControl = function () { slider.hero.addEventListener('wheel', e => { if (slider.idle) { const direction = e.deltaY > 0 ? 'next' : 'prev'; stopAutoplay(); changeSlide(direction); } }); }
é um retorno de chamada que atualiza o indicador deslizante com base no activeIndex.stopAutoplay
callback que reinicia a reprodução automática se ela foi interrompida anteriormente pelo usuário.Com base no tamanho da tela, adicionei dois tipos de controles de usuário: roda e toque. Controle de roda:
stopAutoplay
Aqui, ouvimos a roda mesmo e se o controle deslizante estiver no modo inativo (não animando uma mudança de slide no momento) determinamos a direção da roda, invocamos cancelRequestInterval
para interromper a função de reprodução automática, se estiver em andamento, e mudar o slide com base na direção. O const stopAutoplay = function () { slider.autoplay = false; utils().clearRequestInterval(slider.handle); }
função nada mais é que uma função simples que define nosso sinalizador de reprodução automática com o valor falso e cancela nosso intervalo invocando wheelControl
função de utilidade passando o identificador apropriado:
touchControl
Semelhante a const touchControl = function () { const touchStart = function (e) { slider.ts = parseInt(e.changedTouches[0].clientX); window.scrollTop = 0; } const touchMove = function (e) { slider.tm = parseInt(e.changedTouches[0].clientX); const delta = slider.tm - slider.ts; window.scrollTop = 0; if (slider.idle) { const direction = delta <0 ? 'next' : 'prev'; stopAutoplay(); changeSlide(direction); } } slider.hero.addEventListener('touchstart', touchStart); slider.hero.addEventListener('touchmove', touchMove); }
, temos touchstart
que cuida dos gestos de toque:
touchmove
Ouvimos dois eventos: slideChange
e slideChange
. Então, calculamos a diferença. Se ele retornar um valor negativo, passamos para o próximo slide conforme o usuário desliza da direita para a esquerda. Por outro lado, se o valor for positivo, significando que o usuário deslizou da esquerda para a direita, acionamos div
com direção passada como 'anterior'. Em ambos os casos, a funcionalidade de reprodução automática é interrompida.
Esta é uma implementação de gesto do usuário muito simples. Para desenvolver isso, podemos adicionar os botões anterior / próximo para acionar abs-mask
ao clicar ou adicionar uma lista com marcadores para ir diretamente para um slide com base em seu índice.
media query css para todos os dispositivos
Então aí está, uma maneira pura CSS / JS de codificar um layout de controle deslizante não padrão com efeitos de transição modernos.
Espero que você ache essa abordagem útil como forma de pensar e possa usar algo semelhante em seus projetos de front-end ao codificar para um projeto que não foi necessariamente projetado de forma convencional.
Para aqueles de vocês interessados no efeito de transição de imagem, vou abordar isso nas próximas linhas.
Se revisitarmos a estrutura HTML dos slides que forneci na seção de introdução, veremos que cada slide de imagem tem um div
em torno dele com a classe CSS de overflow:hidden
. O que isso &.prev { z-index: 5; transform: translate3d(-100%, 0, 0); transition: 1s $easing; .abs-mask { transform: translateX(80%); transition: 1s $easing; } }
faz é que oculta uma parte da imagem visível em uma certa quantidade usando abs-mask
e deslocando-o em uma direção diferente da imagem. Por exemplo, se olharmos para a forma como o slide anterior está codificado:
O slide anterior tem um deslocamento de -100% em seu eixo X, movendo-o para a esquerda do slide atual, no entanto, o
interno | div é traduzido 80% para a direita, fornecendo uma janela de visualização mais estreita. Isso, em combinação com ter um Z-index maior para o slide ativo, resulta em uma espécie de efeito de cobertura - a imagem ativa cobre o anterior enquanto, ao mesmo tempo, estende sua área visível movendo a máscara que fornece a visualização completa.As propriedades CSS mais padrão que podemos animar são transformar, opacidade, cor, cor de fundo, altura, largura, etc. A lista completa pode ser encontrada na documentação técnica do Mozilla.
Uma animação de quadro-chave CSS é uma representação em tempo de 0-100% de todas as transições que devem ocorrer no elemento selecionado por um período de tempo especificado. Desta forma, várias transições podem ser combinadas em uma representação visual perfeita.
Uma transição é uma propriedade que permite que as propriedades CSS façam a transição entre dois valores. Por exemplo, passar o mouse sobre um elemento e deixá-lo fazer a transição de sua opacidade de 0 para 1.
Ao interpretar a especificidade, o navegador decide qual regra CSS interpretar. A especificidade do CSS depende dos tipos de seletor: seletores de tipo, seletores de classe, seletores de ID. Combinar vários seletores, adicionar combinadores irmãos e filhos também manipula a especificidade.
Para obter seu conteúdo HTML estilizado por CSS, o navegador usa o seguinte método. Ao carregar o HTML, ele combina seu conteúdo com as informações de estilo fornecidas, cria uma árvore DOM e, finalmente, exibe seu conteúdo.