AngularJS: Animações de rolagem

Introdução

Todo mundo já viu animações baseadas em rolagem, certo? Você sabe, aqueles em que você começa a rolar para baixo na página da web e as animações começam a disparar dependendo de quanto você rola. Um dos meus exemplos favoritos é Let’s free Congress .

Agora, às vezes queremos acionar uma animação, mas não queremos que a página inteira dependa do scroll … talvez, apenas uma pequena parte dele. No entanto, não podemos acionar a animação até que o usuário esteja visualizando a parte que queremos animar, ou então a animação fará toda a sua mágica sem nenhum público. Como fazemos isso?

A diretiva scrollPosition

Deixe-me apresentar a diretiva scrollPosition.

.directive('scrollPosition', ['$window', '$timeout', '$parse', function($window, $timeout, $parse) {
return function(scope, element, attrs) {

var windowEl = angular.element($window)[0];
var directionMap = {
"up": 1,
"down": -1,
"left": 1,
"right": -1
};

// We retrieve the element with the scroll
scope
.element = angular.element(element)[0];

// We store all the elements that listen to this event
windowEl
._elementsList = $window._elementsList || [];
windowEl
._elementsList.push({element: scope.element, scope: scope, attrs: attrs});

var element, direction, index, model, scrollAnimationFunction, tmpYOffset = 0, tmpXOffset = 0;
var userViewportOffset = 200;

function triggerScrollFunctions() {

for (var i = windowEl._elementsList.length - 1; i >= 0; i--) {
element
= windowEl._elementsList[i].element;
if(!element.firedAnimation) {
directionY
= tmpYOffset - windowEl.pageYOffset > 0 ? "up" : "down";
directionX
= tmpXOffset - windowEl.pageXOffset > 0 ? "left" : "right";
tmpXOffset
= windowEl.pageXOffset;
tmpYOffset
= windowEl.pageYOffset;
if(element.offsetTop - userViewportOffset < windowEl.pageYOffset && element.offsetHeight > (windowEl.pageYOffset - element.offsetTop)) {
model
= $parse(windowEl._elementsList[i].attrs.scrollAnimation)
scrollAnimationFunction
= model(windowEl._elementsList[i].scope)
windowEl
._elementsList[i].scope.$apply(function() {
element
.firedAnimation = scrollAnimationFunction(directionMap[directionX]);
})
if(element.firedAnimation) {
windowEl
._elementsList.splice(i, 1);
}
}
} else {
index
= windowEl._elementsList.indexOf(element); //TODO: Add indexOf polyfill for IE9
if(index > 0) windowEl._elementsList.splice(index, 1);
}
};
};
windowEl
.onscroll = triggerScrollFunctions;
};
}]);

Essa diretiva foi usada para criar a seguinte animação .

Cenário

Basicamente, quando você rola a página para baixo, o controle deslizante começa a subir até atingir o limite, e a animação só é acionada quando o usuário está visualizando o elemento a ser animado.

Uso

Primeiro, vamos ver como podemos usar essa diretiva em seu projeto e, em seguida, vamos quebrá-la em pequenos pedaços para entender como funciona.

No elemento que você deseja executar uma animação, adicione a diretiva. No nosso caso, foi este (Jade HTML):

.row.show-for-large-up
.teaser(scroll-position, scroll-animation='fireupApplicationDesignAnimation')
.row
.large-6.columns.left-align.margin-top
h1
(data-i18n="_CareerDesign_APPLICATIONDESIGNTITLE")

Isso diz ao AngularJS que, quando o usuário começar a rolar dentro da área desse elemento DOM, ele acionará a função de animação de rolagem. Em seu controlador, você deve ter algo assim

$scope.fireupApplicationDesignAnimation = function(scrollDirection) {
scrollDirection
> 0 ? reduceAmount() : aumentAmount(); // We want to increase on scrollDown
setOffsetForImage
();
};

A diretiva envia para a função do controlador a direção da rolagem, então você pode até mesmo executar animações com base nisso. Às vezes, você precisa acionar a animação apenas uma vez, então você precisa retornar truepara fazer isso. Um exemplo da mesma página:

$scope.fireupMarketingDesignAnimation = function() {
if(!firedMarketingAnimation) {
window
.animations.marketingAnimation.init();
firedMarketingAnimation
= true;
return firedMarketingAnimation;
}
}

Neste, temos uma animação que só precisa ser acionada uma vez. Como não sabemos em qual ponto da rolagem o usuário corresponderá à janela de visualização DOM (lembre-se, ele pode rolar muito rápido!), Devemos envolver nossa função em um sinalizador que garanta que isso aconteça apenas uma vez.

Como funciona?

Esta diretiva realiza o seguinte:

  • Cria uma matriz de elementos que requerem um onScroll eventListener (você pode ter múltiplos, eles serão armazenados e removidos no caso de você retornar um truevalor de sua função de acionamento para garantir o uso de memória)
  • Adiciona um eventLister onScroll que verifica a posição atual do usuário na webage
  • Viaja por todos os seus elementos vinculados (embora eu não tenha tido problemas de desempenho, isso pode ser um problema se você tiver muitos elementos vinculados com a diretiva) e verifica se eles acionaram sua animação.
  • Se não o fizeram e o usuário está dentro da janela de visualização do pergaminho, eles o fazem. Isso faz muito uso do $parse, então certifique-se de ler seus documentos para entender completamente.
  • Exibe o elemento que foi vinculado à diretiva se a animação chamada dentro da função do escopo retornou um valor verdadeiro.

Conclusão

Essa diretiva é boa quando você precisa executar muitas animações que são disparadas por um período de tempo específico. Também é eficaz quando você deseja um comportamento específico que depende da posição do usuário ou até mesmo alterar os valores CSS3 conforme você rola. Fiz um Codepen para mostrar isso, que também funciona em scroll horizontal.

Cenário

Basicamente, estou movendo e girando a bola em cada pergaminho, dependendo da direção em que o usuário vai.

Esteja avisado, porém, que se o usuário tiver uma tela que permite que ele veja todo o seu conteúdo sem rolar a tela, ele pode não conseguir ver o programa. Preencha um tempo limite para garantir que ele o faça. Como nota final, estou usando indexOf, que não é compatível com o IE, mas pode ser facilmente substituído por um for..loop simples.