efeito-antes-e-depois-sem-jquery-com-gulp-stylus.jpg

Sabe aquele efeito antes e depois interativo, bastante usado em sites como G1? Ele é bem simples e envolve alguns conceitos muito úteis, vamos fazer um?

Mas calma. Se você está aqui pelo #1postperday, sabe que não posso postar qualquer coisa. Tem que ser maneiro.

Nosso pequeno plugin, além de bonitinho, será reutilizável e carregará automaticamente, quantas instâncias do efeito na mesma página forem necessárias.

Além disso, vamos usar stylus e gulp em nosso workflow. Você pode fazer com css direto e sem livereload, mas aí eu te pergunto: Pra quê?

Analisando a demanda

  • Necessidade: Plugin reutilizável de efeito antes-e-depois interativo: usuário deve poder analisar e interagir com o efeito.

  • Requisitos: Mobile-ready e responsivo, leve e sem libraries externas (ex: jquery) em produção. Deve ser possível criar novas instâncias somente pelo markup. O markup deve ser mínimo.


Demo

A idéia é que fique assim ao final. Você pode incrementar, mas mantive o mais simples e básico possível, já que o objetivo é entender o processo, e não duplicar pra sair usando por aí sem saber. (Se fosse assim, usavamos JQuery hehe)

See the Pen Antes e Depois SEM Jquery by Romulo_Ctba (@romuloctba) on CodePen.

Curtiu? Vamos lá?

Ah, o resultado final está também (com gulp e stylus) neste repositório do github


Planejando o Workflow / Porque usar Stylus e Gulp

Se você não quiser usar GULP, pule direto para Criando os arquivos e pastas mais abaixo. Veja o conteúdo do CSS no codepen acima, clicando em ‘compile css’, mas eu recomendo que faça essa parte, pois é bem maneira.

  • Criaremos nosso código em um pasta chamada /src, dentro do diretório do nosso projeto.

  • Escreveremos o Css usando Stylus, dentro da pasta /src/stylus

  • O Javascript, na pasta /src/javascript

  • Usaremos o Gulp para compilar o stylus em CSS e copiar tudo (css, html e imagens) para uma pasta chamda /build.
    Nela, ficará o conteúdo final de nosso projeto.
    Ela ficará no diretório de nosso projeto, ao lado da /src.

  • AH, os arquivos css compilados ficarão na pasta /build/css. A pasta /stylus só existe em nosso source

A vantagem disso é que podemos criar tarefas de gulp para minificar o css e o javascript, bem como otimizar imagens e uma infinidade de outras coisas que podem ser muito úteis, de forma automática.


Preparando o Workflow

Vamos criar algumas tarefas do Gulp para nos ajudar com o Stylus, e também para o liveReload.

Se você ainda não tem, instale o NodeJS e, depois, abra seu terminal e rode:

[talvez você precise ser administrador ou rodar como sudo]
1
npm install -g gulp

Agora, crie uma pasta para seu projeto. Nela, um novo arquivo, chamado package.json. Ele contém informações básicas sobre nosso projeto para o NodeJs.

package.json - crie na pasta do projeto
1
2
3
4
5
6
7
8
9
{
"name": "antes-depois-sem-jquery",
"version": "1.0.0",
"devDependencies": {
"gulp": "^3.8.10",
"gulp-connect-multi": "^1.0.8",
"gulp-stylus": "^1.3.4"
}

}

Nele declaramos as devDependencies, ou seja: dependências que não serão usadas em produção, apenas enquanto estamos desenvolvendo.
Elas são: Gulp (local, pois já instalamos o global), gulp-connect-multi (será o servidor com liveReload) e gulp-stylus que vai transformar Stylus em Css.

Com o terminal, acesse a pasta do projeto e rode:

1
npm install

Criando nossas tarefas para o Gulp

Precisaremos criar 3 tarefinhas: stylus, arquivos e servidor. Vamos lá:

Gulp task stylus

Crie um arquivo chamado gulpfile.js na pasta do seu projeto. Nele, insira:

gulpfile.js [crie o arquivo, e insira:]
1
2
3
4
5
6
7
8
9
10
11
var gulp = require('gulp')
, stylus = require('gulp-stylus')
, connect = require('gulp-connect-multi')();

gulp.task('stylus', function(){
gulp.src('src/stylus/*.styl')
.pipe(stylus({
compress: true
}))
.pipe(gulp.dest('./build/css'))
})

Nesta task, pegamos todos os arquivos .styl da pasta .src/stylus/ e aplicando, com o .pipe(), p método stylus.
A opção compress:true vai permitir que ele minifique o css enquanto copia, com o gulp.dest() para a pasta /build/css.

Gulp task arquivos

´Tudo o que ela faz é copiar arquivos. Ela vai copiar tudo, menos o que estiver na pasta src/stylus. Insira ao final do gulpfile:

gulpfile.js [insira ao final do arquivo]
1
2
3
4
gulp.task('arquivos', function(){
gulp.src(['./src/**/**', '!src/stylus'])
.pipe(gulp.dest('./build'))
})

Gulp task servidor

Vamos inciar o gulp-connect-multi, com a opção liveReload como true. A opção root é a pasta principal. No caso, a pasta build.

gulpfile.js [insira ao final do arquivo]
1
2
3
4
5
6
7
8
gulp.task('servidor', connect.server({
root: ['build'],
port: 1337,
livereload: true,
open: {
browser: 'chrome' //ou firefox ou safari... se for OSX é 'Google Chrome'
}
}))

Task padrão, para fazer tudo:

Faremos a task default executar todas as outras, por isso, crie:

gulpfile.js [insira ao final do arquivo]
1
gulp.task('default', ['stylus', 'arquivos', 'servidor']);

Com as tasks prontas, podemos criar nosso código fonte.


Criando os arquivos e pastas

Vamos criar nossa estrutura. Dentro da pasta do seu projeto, crie uma pasta chamada src.

Dentro dela, 3 pastas: img, js e stylus.

Agora sim! Vamos ‘começar’.

Arquivo src/index.html

Crie o arquivo index.html dentro da pasta src com a seguinte estrutura básica:

index.html [Crie na pasta src e insira:]
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Antes e Depois com Gulp, Stylus e SEM JQuery.</title>
</head>
<body>
<link rel="stylesheet" href="css/main.css">
<!-- conteudo irá aqui -->

<script src="js/main.js" type="text/javascript"></script>
</body>
</html>

Acima nada mais é do que o ‘esqueleto’ do nosso HTML, incluindo os arquivos css/main.css e js/main.js.

Um dos requisitos da nossa demanda era o markup mínimo.
Pois assim será, insira após o comentário:

index.html [Insira após o comentário]
1
2
3
4
5
6
7
(...)
<!-- conteudo irá aqui -->
<div class="antesDepois">
<img src="img1.jpg" alt="imagem 1" class="antes">
<div class="depois" data-src="img2.jpg"></div>
</div>
(...)

Substitua img1.jpg e img2.jpg pelo nome das suas imagens.
Se quiser, pode usar as minhas imagens, salve elas na pasta src/img. Pegue elas aqui: http://imgur.com/a/Hcc8R .

Olhem como o markup é mínimo! Um div com a classe .antesDepois é o nosso container. Dentro dele, uma img, que será o antes, e um div com o atributo data-src apontando uma outra imagem, que será o depois.

Com JS, pegaremos este atributo e adicionaremos como background do div, fazendo a mágica acontecer.

Mas, pra isso, precisamos dum pouco de CSS:

Arquivo src/stylus/main.styl

Crie o arquivo main.styl dentro da pasta src/stylus/

main.styl [crie o arquivo e insira o conteúdo]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.antesDepois
display block
position relative
width 100%
max-width 100% !important
cursor col-resize
img
max-width 100% !important
.depois
position absolute
top 0
left 0
width 45%
height 99%
background-size cover
border-right 1px solid #fff

Tudo bem simples: A classe do container é .antesDepois. Estamos definindo posicionamento relativo e largura 100%. e !important no max-width, pois não queremos que ele passe de 100% de largura.

O cursor col-resize é pra dar um charminho.

Também colocamos max-width: 100% !important para o img DENTRO DO .antesDepois. Apenas as imgs de dentro dele terão essa regra.

A classe .depois é quem faz a mágica.
Definimos um posicionamento absoluto, com 45% de largura e 99% de altura.
O Background-size vai ser cover, e uma bordinha branca pra ficar legal.

Note que o backgorund será incluído agora:

Arquivo src/js/main.js

O nosso Javascript não usa Jquery.

Criaremos uma função IIFE, ou seja: Uma função auto-executável, que vai pegar todos os elementos da classe .antesDepois e executar um loop for com eles, gerando cada uma das instâncias que houver no HTML.

A sintaxe de uma função IIFE é assim: (function(){})() e seu conteúdo vai dentro dos {}.

main.js [Crie na pasta js e insira:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

(function(){
var containers = document.getElementsByClassName('antesDepois');

for (i=0, qtd=containers.length; i < qtd; i++) {

console.log('Criada Instância ' + i);

var container = document.getElementsByClassName('antesDepois')[i]
, antes = container.getElementsByClassName('antes')[0]
, depois = container.getElementsByClassName('depois')[0]
, depoisImg = depois.getAttribute('data-src');

depois.style.backgroundImage = "url('"+depoisImg+"')";
container.addEventListener("mousemove", comparaPosicao, false);
container.addEventListener("touchstart",comparaPosicao,false);
container.addEventListener("touchmove",comparaPosicao,false);
}
})()

Note a var containers: O método getElementsByCLassName() pega TODOS os elementos com a classe antesDepois e coloca-os em um Array.
Com isso, podemos fazer um loop FOR, que vai interagir, executando em cada um dos itens do array.

Em nosso for, colocamos o containers.length em uma variável. Isso é tipo um ‘cache’ pra melhorar a performance ao iterar um array.

Em cada um dos loops, pegaremos o item da vez no array container.

Daí as variáveis antes, depois e depoisImg vêm com o mesmo método getElementsByClassName(), só que dessa vez o método é executado dentro do container, e não do document.

Portanto, só os ementos que estiverem dentro de container, que representa o item atual do FOR.

Por último, no trecho acima, temos o método .style(), em que inserimos um background-image, cuja url pegamos com getAttribute('data-src') logo antes.

Daí, os 3 addEventListeners que vão monitorar, respectivamente por um Mousemove, TouchStart e TouchMove, sempre dentro do container.

Quando disparar o evento, vão chamar a função comparaPosicao(), que criaremos agora:

Insira, ao final do main.js:

main.js [insira, ao final do arquivo:]
1
2
3
4
5
6
7
function comparaPosicao(e){
var retangulo = this.getBoundingClientRect();
var posicao = ((e.pageX - retangulo.left) / this.offsetWidth)*100;
if (posicao <= 100) {
this.getElementsByClassName('depois')[0].style.width= posicao+"%";
}
}

A função recebe um evento (e) como parâmetro. Aì usamos this.getBoundingClientRect() que nos dá um retângulo do container. A palavra chave this, no caso, refere-se à variavel container, onde a função foi chamada pelo ´eventListener`.

A var posicao é uma continha que pega o eixo X de dentro do retangulo (pageX - retangulo.left) e divide pelo offsetWidth, ou seja: largura do container. Isso dá um número decimal, fazemos ele * 100 e temos em quantos % da largura nosso mouse está.

Se a var posicao for menor ou igual a 100, pegaremos o elemento com a classe .depois de dentro do container atual (this) e definiremos sua largura (width) com a posição do mouse. Se ela for maior, significa que o mouse ou touch está fora do container.

Ufa, terminamos.

Testando e Rodando.

Com o seu terminal, acesse a pasta d oprojeto e rode o comendo:

1
gulp

Se tudo der certo, seu navegador abrirá em http://localhost:1337 com tudo funcionando!! :)

Ah, o resultado final está também (com gulp e stylus) neste repositório do github


Conclusão

Apesar da preparação para o Workflow ter deixado o artigo um pouco longo, vimos que o código é bastante simples, sem precisar de JQuery.

Espero que possa passar à quem ainda tem medo, a coragem necessária pra largar mão - quando possível - desse framework tão bom, mas tão usado desnecessariamente.

É isso aí! Muito obrigado pela visita, volte sempre e fique a vontade :D