vue-direction-hover
Version:
Vue.js Component for the Direction Hover Effect.
216 lines (173 loc) • 9.06 kB
JavaScript
(function(window){
'use strict';
function defineDirectionalHover(){
var self = {};
// Default Options
var options = {
item: '.dh__item', // The Item Element
container: '#dh__container', // The Container of the items
overlay: '.dh__overlay', // The Overlay Element inside the item
transition: 'easeInOutCubic', // Transition Type
speed: 300 // Transition Speed
};
// Easing Function
const EasingFunctions = {
// no easing, no acceleration
'linear': function (t) { return t },
// accelerating from zero velocity
'easeInQuad': function (t) { return t*t },
// decelerating to zero velocity
'easeOutQuad': function (t) { return t*(2-t) },
// acceleration until halfway, then deceleration
'easeInOutQuad': function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t },
// accelerating from zero velocity
'easeInCubic': function (t) { return t*t*t },
// decelerating to zero velocity
'easeOutCubic': function (t) { return (--t)*t*t+1 },
// acceleration until halfway, then deceleration
'easeInOutCubic': function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 },
// accelerating from zero velocity
'easeInQuart': function (t) { return t*t*t*t },
// decelerating to zero velocity
'easeOutQuart': function (t) { return 1-(--t)*t*t*t },
// acceleration until halfway, then deceleration
'easeInOutQuart': function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t },
// accelerating from zero velocity
'easeInQuint': function (t) { return t*t*t*t*t },
// decelerating to zero velocity
'easeOutQuint': function (t) { return 1+(--t)*t*t*t*t },
// acceleration until halfway, then deceleration
'easeInOutQuint': function (t) { return t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t },
// elastic bounce effect at the beginning
'easeInElastic': function (t) { return (.04 - .04 / t) * Math.sin(25 * t) + 1 },
// elastic bounce effect at the end
'easeOutElastic': function (t) { return .04 * t / (--t) * Math.sin(25 * t) },
// elastic bounce effect at the beginning and end
'easeInOutElastic': function (t) { return (t -= .5) < 0 ? (.01 + .01 / t) * Math.sin(50 * t) : (.02 - .01 / t) * Math.sin(50 * t) + 1 }
};
// Get the container/element of the
self.item = options.item;
self.elements = document.querySelectorAll(self.item);
// Create bit flags
const FLAG_T = 1, // top
FLAG_R = 2, // right
FLAG_B = 4, // bottom
FLAG_L = 8; // left
// Create bit masks
const tlMask = FLAG_T | FLAG_L,
trMask = FLAG_T | FLAG_R,
blMask = FLAG_B | FLAG_L,
brMask = FLAG_B | FLAG_R;
self.setOptions = function(opt){
options = opt;
}
self.slideOverlay = function(overlay, direction, px, py, ew, eh, ex, ey) {
var cornerFlags = 0; // top|right|bottom|left
if (py - ey <= eh / 2) cornerFlags ^= FLAG_T;
if (px - ex >= ew / 2) cornerFlags ^= FLAG_R;
if (py - ey > eh / 2) cornerFlags ^= FLAG_B;
if (px - ex < ew / 2) cornerFlags ^= FLAG_L;
self.findSide(cornerFlags, overlay, direction, px-ex, py-ey, ew/2, eh/2);
}
self.findSide = function(flags, overlay, direction, x, y, w, h) {
if (self.testMask(flags, tlMask)) {
self.testTopLeftToBottomRight(x, y, w, h) ? self.animate(overlay, direction, 0, -w*2) : self.animate(overlay, direction, -h*2, 0);
}
else if (self.testMask(flags, trMask)) {
self.testBottomRightToTopLeft(x, y, w, h) ? self.animate(overlay, direction, -h*2, 0) : self.animate(overlay, direction, 0, w*2);
}
else if (self.testMask(flags, blMask)) {
self.testBottomRightToTopLeft(x, y, w, h) ? self.animate(overlay, direction, 0, -w*2) : self.animate(overlay, direction, h*2, 0);
}
else if (self.testMask(flags, brMask)) {
self.testTopLeftToBottomRight(x, y, w, h) ? self.animate(overlay, direction, h*2, 0) : self.animate(overlay, direction, 0, w*2);
}
}
self.testMask = function(flags, mask) {
return (flags & mask) === mask;
}
self.testTopLeftToBottomRight = function(x, y, w, h) {
return (h * x - w * y) < 0;
}
self.testBottomRightToTopLeft = function(x, y, w, h) {
return (w * (y-h) + h * x - w * h) < 0;
}
self.animate = function (overlay, direction, top, left){
// Set the transition to the overlay
if (direction === 'in') {
// First bring the overlay to the position
overlay.style.top = top + 'px';
overlay.style.left = left + 'px';
// Animate the Overlay to {0, 0} positoin
self.animateHelper(function(t){
var currTop = parseFloat(overlay.style.top.replace(/(px|%)$/, '')),
currLeft = parseFloat(overlay.style.left.replace(/(px|%)$/, ''));
overlay.style.top = (currTop - (currTop * t)) + 'px';
overlay.style.left = (currLeft - (currLeft * t)) + 'px';
});
}else if(direction == 'out'){
// Bring the overlay to {0, 0} position
overlay.style.top = 0;
overlay.style.left = 0;
// Animate the Overlay to {top, left} positoin
self.animateHelper(function(t){
overlay.style.top = (top * t) + 'px';
overlay.style.left = (left * t) + 'px';
});
}
}
self.animateHelper = function(callback, duration = options.speed, easing = EasingFunctions[options.easing]) {
// Total Duration of the Animation and the Animation function
if (!duration) duration = 300;
if (!easing) easing = EasingFunctions['linear'];
var start = (new Date).getTime();
var animation = function() {
var t = easing(((new Date).getTime()-start)/duration);
t <= 1 ? requestAnimationFrame(animation) : t = 1;
callback(t, t === 1);
};
animation();
}
// Loop through the elements and initialize the effect
for(var i = 0; i < self.elements.length; i++){
// Current item
let item = self.elements[i];
// Set Initial CSS Attributes to the Item
item.style.position = 'relative';
item.style.overflow = 'hidden';
// Set Initial CSS Attributes to the overlay
var child = item.querySelector(options.overlay);
child.style.position = 'absolute';
child.style.top = '-' + item.offsetHeight + 'px';
child.style.left = '-' + item.offsetWidth + 'px';
// Add the Events
item.addEventListener('mouseenter', function(e){
// Get the target and the Overlay
child = e.target.querySelector(options.overlay);
item = e.target;
// If they are in a container (fix for if you want to have the effect in lets say container that isnt 100% width)
var ex = e.pageX - item.parentNode.offsetLeft;
var ey = e.pageY - item.parentNode.offsetTop;
// On entering with the mouuse, initialize the 'in' effect (sliding in)
self.slideOverlay(child, 'in', ex, ey, item.offsetWidth, item.offsetHeight,
Math.floor(item.offsetLeft), item.offsetTop, options);
});
item.addEventListener('mouseleave', function(e){
// Get the target and the Overlay
child = e.target.querySelector(options.overlay);
item = e.target;
// If they are in a container (fix for if you want to have the effect in lets say container that isnt 100% width)
var ex = e.pageX - item.parentNode.offsetLeft;
var ey = e.pageY - item.parentNode.offsetTop;
// On entering with the mouuse, initialize the 'out' effect (sliding out)
self.slideOverlay(child, 'out', ex, ey, item.offsetWidth, item.offsetHeight,
Math.floor(item.offsetLeft), item.offsetTop, options);
});
};
return self;
}
//define globally if it doesn't already exist
if(typeof(Library) === 'undefined'){
window.DirectionalHover = defineDirectionalHover();
}
})(window);