storm-wall
Version:
Interactive animating content wall
394 lines (333 loc) • 14 kB
JavaScript
/**
* @name storm-wall: Interactive animating content wall
* @version 1.2.4: Tue, 09 Apr 2019 08:27:53 GMT
* @author stormid
* @license MIT
*/
(function(root, factory) {
var mod = {
exports: {}
};
if (typeof exports !== 'undefined'){
mod.exports = exports
factory(mod.exports)
module.exports = mod.exports.default
} else {
factory(mod.exports);
root.StormWall = mod.exports.default
}
}(this, function(exports) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
function unwrapExports(x) {
return x && x.__esModule ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var rafThrottle_1 = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
var rafThrottle = function rafThrottle(callback) {
var requestId = void 0;
var later = function later(context, args) {
return function () {
requestId = null;
callback.apply(context, args);
};
};
var throttled = function throttled() {
if (requestId === null || requestId === undefined) {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
requestId = requestAnimationFrame(later(this, args));
}
};
throttled.cancel = function () {
return cancelAnimationFrame(requestId);
};
return throttled;
};
exports.default = rafThrottle;
});
var throttle = unwrapExports(rafThrottle_1);
//http://goo.gl/5HLl8
var easeInOutQuad = function easeInOutQuad(t, b, c, d) {
t /= d / 2;
if (t < 1) {
return c / 2 * t * t + b;
}
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
};
var move = function move(amount) {
document.documentElement.scrollTop = amount;
document.body.parentNode.scrollTop = amount;
document.body.scrollTop = amount;
};
var position = function position() {
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop;
};
var scrollTo = function scrollTo(to) {
var duration = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 500;
var callback = arguments[2];
var start = position(),
change = to - start,
currentTime = 0,
increment = 20,
animateScroll = function animateScroll() {
currentTime += increment;
var val = easeInOutQuad(currentTime, start, change, duration);
move(val);
if (currentTime < duration) window.requestAnimationFrame(animateScroll);else callback && typeof callback === 'function' && callback();
};
animateScroll();
};
var inView = function inView(element, view) {
var box = element.getBoundingClientRect();
return box.right >= view.l && box.bottom >= view.t && box.left <= view.r && box.top <= view.b;
};
var defaults = {
classNames: {
ready: '.js-wall--is-ready',
trigger: '.js-wall-trigger',
item: '.js-wall-item',
content: '.js-wall-child',
panel: '.js-wall-panel',
panelInner: '.js-wall-panel-inner',
open: '.js-wall--is-open',
animating: '.js-wall--is-animating',
closeButton: '.js-wall-close',
nextButton: '.js-wall-next',
previousButton: '.js-wall-previous'
},
offset: 120
};
var CONSTANTS = {
ERRORS: {
ROOT: 'Wall cannot be initialised, no trigger elements found',
ITEM: 'Wall item cannot be found',
TRIGGER: 'Wall trigger cannot be found'
},
KEYCODES: [13, 32],
EVENTS: ['click', 'keydown']
};
var StormWall = {
init: function init() {
var _this = this;
this.openIndex = false;
this.initThrottled();
this.initItems();
this.initTriggers();
this.initPanel();
this.initButtons();
window.addEventListener('resize', this.throttledResize.bind(this));
setTimeout(this.equalHeight.bind(this), 100);
this.node.classList.add(this.settings.classNames.ready.substr(1));
setTimeout(function () {
if (!!window.location.hash && !!~document.getElementById(window.location.hash.slice(1)).className.indexOf(_this.settings.classNames.trigger.substr(1))) document.getElementById(window.location.hash.slice(1)).click();
}, 260);
return this;
},
initThrottled: function initThrottled() {
var _this2 = this;
this.throttledResize = throttle(function () {
_this2.equalHeight(_this2.setPanelTop.bind(_this2));
});
this.throttledChange = throttle(this.change);
this.throttledPrevious = throttle(this.previous);
this.throttledNext = throttle(this.next);
},
initTriggers: function initTriggers() {
var _this3 = this;
this.items.forEach(function (item, i) {
var trigger = item.node.querySelector(_this3.settings.classNames.trigger);
if (!trigger) throw new Error(CONSTANTS.ERRORS.TRIGGER);
CONSTANTS.EVENTS.forEach(function (ev) {
trigger.addEventListener(ev, function (e) {
if (e.keyCode && !~CONSTANTS.KEYCODES.indexOf(e.keyCode)) return;
_this3.throttledChange(i);
e.preventDefault();
});
});
});
},
initPanel: function initPanel() {
var elementFactory = function elementFactory(element, className, attributes) {
var el = document.createElement(element);
el.className = className;
for (var k in attributes) {
if (attributes.hasOwnProperty(k)) {
el.setAttribute(k, attributes[k]);
}
}
return el;
},
panelElement = elementFactory(this.items[0].node.tagName.toLowerCase(), this.settings.classNames.panel.substr(1), { 'aria-hidden': true });
this.panelInner = elementFactory('div', this.settings.classNames.panelInner.substr(1));
this.panel = this.node.appendChild(panelElement);
return this;
},
initButtons: function initButtons() {
var _this4 = this;
var buttonsTemplate = '<button class="' + this.settings.classNames.closeButton.substr(1) + '" aria-label="close">\n\t\t\t\t\t\t\t\t<svg fill="#000000" height="30" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t\t\t\t\t<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>\n\t\t\t\t\t\t\t\t\t<path d="M0 0h24v24H0z" fill="none"/>\n\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t \t\t<button class="' + this.settings.classNames.previousButton.substr(1) + '" aria-label="previous">\n\t\t\t\t\t\t\t\t <svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t\t\t\t\t\t<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>\n\t\t\t\t\t\t\t\t\t\t<path d="M0 0h24v24H0z" fill="none"/>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t \t\t<button class="' + this.settings.classNames.nextButton.substr(1) + '" aria-label="next">\n\t\t\t\t\t\t\t\t\t<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t\t\t\t\t\t<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>\n\t\t\t\t\t\t\t\t\t\t<path d="M0 0h24v24H0z" fill="none"/>\n\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t </button>';
this.panel.innerHTML = '' + this.panel.innerHTML + buttonsTemplate;
CONSTANTS.EVENTS.forEach(function (ev) {
_this4.panel.querySelector(_this4.settings.classNames.closeButton).addEventListener(ev, function (e) {
if (e.keyCode && !~CONSTANTS.KEYCODES.indexOf(e.keyCode)) return;
_this4.close.call(_this4);
});
_this4.panel.querySelector(_this4.settings.classNames.previousButton).addEventListener(ev, function (e) {
if (e.keyCode && !~CONSTANTS.KEYCODES.indexOf(e.keyCode)) return;
_this4.throttledPrevious.call(_this4);
});
_this4.panel.querySelector(_this4.settings.classNames.nextButton).addEventListener(ev, function (e) {
if (e.keyCode && !~CONSTANTS.KEYCODES.indexOf(e.keyCode)) return;
_this4.throttledNext.call(_this4);
});
});
},
initItems: function initItems() {
var _this5 = this;
var items = [].slice.call(this.node.querySelectorAll(this.settings.classNames.item));
if (items.length === 0) throw new Error(CONSTANTS.ERRORS.ITEM);
this.items = items.map(function (item) {
return {
node: item,
content: item.querySelector(_this5.settings.classNames.content),
trigger: item.querySelector(_this5.settings.classNames.trigger)
};
});
},
change: function change(i) {
var _this6 = this;
if (this.openIndex === false) return this.open(i);
if (this.openIndex === i) return this.close();
if (this.items[this.openIndex].node.offsetTop === this.items[i].node.offsetTop) this.close(function () {
return _this6.open(i, _this6.panel.offsetHeight);
}, this.panel.offsetHeight);else this.close(function () {
return _this6.open(i);
});
},
open: function open(i, start, speed) {
var _this7 = this;
this.panelSourceContainer = this.items[i].content;
this.openIndex = i;
this.setPanelTop();
this.panelContent = this.panelSourceContainer.firstElementChild.cloneNode(true);
this.panelInner.appendChild(this.panelContent);
this.panelSourceContainer.removeChild(this.panelSourceContainer.firstElementChild);
this.panel.insertBefore(this.panelInner, this.panel.firstElementChild);
var currentTime = 0,
panelStart = start || 0,
totalPanelChange = this.panel.offsetHeight - panelStart,
rowStart = this.closedHeight + panelStart,
totalRowChange = totalPanelChange,
duration = speed || 16,
animateOpen = function animateOpen() {
currentTime++;
_this7.panel.style.height = easeInOutQuad(currentTime, panelStart, totalPanelChange, duration) + 'px';
_this7.resizeRow(_this7.items[_this7.openIndex].node, easeInOutQuad(currentTime, rowStart, totalRowChange, duration) + 'px');
if (currentTime < duration) window.requestAnimationFrame(animateOpen.bind(_this7));else {
_this7.panel.style.height = 'auto';
_this7.items[i].node.parentNode.insertBefore(_this7.panel, _this7.items[i].node.nextElementSibling);
!!window.history && !!window.history.pushState && window.history.pushState({ URL: '#' + _this7.items[i].trigger.getAttribute('id') }, '', '#' + _this7.items[i].trigger.getAttribute('id'));
if (!inView(_this7.panel, function () {
return {
l: 0,
t: 0,
b: (window.innerHeight || document.documentElement.clientHeight) - _this7.panel.offsetHeight,
r: window.innerWidth || document.documentElement.clientWidth
};
})) scrollTo(_this7.panel.offsetTop - _this7.settings.offset);
}
};
this.node.classList.add(this.settings.classNames.open.substr(1));
this.panel.removeAttribute('aria-hidden');
this.items[i].trigger.setAttribute('aria-expanded', true);
animateOpen.call(this);
return this;
},
close: function close(cb, end, speed) {
var _this8 = this;
var endPoint = end || 0,
currentTime = 0,
panelStart = this.panel.offsetHeight,
totalPanelChange = endPoint - panelStart,
rowStart = this.items[this.openIndex].node.offsetHeight,
totalRowChange = totalPanelChange,
duration = speed || 16,
animateClosed = function animateClosed() {
currentTime++;
_this8.panel.style.height = easeInOutQuad(currentTime, panelStart, totalPanelChange, duration) + 'px';
_this8.resizeRow(_this8.items[_this8.openIndex].node, easeInOutQuad(currentTime, rowStart, totalRowChange, duration) + 'px');
if (currentTime < duration) window.requestAnimationFrame(animateClosed.bind(_this8));else {
if (!endPoint) _this8.panel.style.height = 'auto';
_this8.panelInner.removeChild(_this8.panelContent);
_this8.panel.setAttribute('aria-hidden', true);
_this8.items[_this8.openIndex].trigger.setAttribute('aria-expanded', false);
_this8.panelSourceContainer.appendChild(_this8.panelContent);
_this8.node.classList.remove(_this8.settings.classNames.animating.substr(1));
_this8.node.classList.remove(_this8.settings.classNames.open.substr(1));
_this8.openIndex = false;
if (typeof cb === 'function') cb();else !!window.history && !!window.history.pushState && history.pushState('', document.title, window.location.pathname + window.location.search);
}
};
this.node.classList.add(this.settings.classNames.animating.substr(1));
animateClosed.call(this);
},
previous: function previous() {
return this.change(this.openIndex - 1 < 0 ? this.items.length - 1 : this.openIndex - 1);
},
next: function next() {
return this.change(this.openIndex + 1 === this.items.length ? 0 : this.openIndex + 1);
},
equalHeight: function equalHeight(cb) {
var _this9 = this;
var openHeight = 0,
closedHeight = 0;
this.items.map(function (item, i) {
item.node.style.height = 'auto';
if (_this9.openIndex !== false && item.node.offsetTop === _this9.items[_this9.openIndex].node.offsetTop) {
if (_this9.openIndex === i) openHeight = item.node.offsetHeight + _this9.panel.offsetHeight;
} else {
if (item.node.offsetHeight > closedHeight) closedHeight = item.node.offsetHeight;
}
return item;
}).map(function (item, i) {
if (_this9.openIndex !== i) item.node.style.height = closedHeight + 'px';
});
this.openHeight = openHeight;
this.closedHeight = closedHeight === 0 ? this.closedHeight : closedHeight;
if (this.openHeight > 0) {
this.resizeRow(this.items[this.openIndex].node, this.openHeight + 'px');
typeof cb === 'function' && cb();
}
},
resizeRow: function resizeRow(el, height) {
this.items.forEach(function (item) {
if (item.node.offsetTop === el.offsetTop) item.node.style.height = height;
});
return this;
},
setPanelTop: function setPanelTop() {
this.panel.style.top = this.items[this.openIndex].node.offsetTop + this.items[this.openIndex].trigger.offsetHeight + 'px';
}
};
var init = function init(sel, opts) {
var els = [].slice.call(document.querySelectorAll(sel));
if (els.length === 0) throw new Error(CONSTANTS.ERRORS.ROOT);
return els.map(function (el) {
return Object.assign(Object.create(StormWall), {
node: el,
settings: Object.assign({}, defaults, opts)
}).init();
});
};
var stormWall = { init: init };
exports.default = stormWall;;
}));