Put simpleParallax and modify logo.php to merge

This commit is contained in:
2020-01-04 18:18:39 +01:00
parent ca6e77ebb0
commit fc231ee294
69 changed files with 3660 additions and 23 deletions

View File

@@ -0,0 +1,9 @@
// check wether the element is a Node List, a HTML Collection or an array
// return an array of nodes
const convertToArray = (elements) => {
if (NodeList.prototype.isPrototypeOf(elements) || HTMLCollection.prototype.isPrototypeOf(elements)) return Array.from(elements);
if (typeof elements === 'string' || elements instanceof String) return document.querySelectorAll(elements);
return [elements];
};
export default convertToArray;

View File

@@ -0,0 +1,13 @@
// Detect css transform
const cssTransform = () => {
const prefixes = 'transform webkitTransform mozTransform oTransform msTransform'.split(' ');
let transform;
let i = 0;
while (transform === undefined) {
transform = document.createElement('div').style[prefixes[i]] !== undefined ? prefixes[i] : undefined;
i += 1;
}
return transform;
};
export default cssTransform();

View File

@@ -0,0 +1,21 @@
// check if image is fully loaded
const isImageLoaded = (image) => {
// check if image is set as the parameter
if (!image) {
return false;
}
// check if image has been 100% loaded
if (!image.complete) {
return false;
}
// check if the image is displayed
if (typeof image.naturalWidth !== 'undefined' && image.naturalWidth === 0) {
return false;
}
return true;
};
export default isImageLoaded;

View File

@@ -0,0 +1,33 @@
class Viewport {
constructor() {
this.positions = {
top: 0,
bottom: 0,
height: 0,
};
}
setViewportTop(container) {
// if this is a custom container, user the scrollTop
this.positions.top = (container ? container.scrollTop : window.pageYOffset);
return this.positions;
}
setViewportBottom() {
this.positions.bottom = this.positions.top + this.positions.height;
return this.positions;
}
setViewportAll(container) {
// if this is a custom container, user the scrollTop
this.positions.top = (container ? container.scrollTop : window.pageYOffset);
// if this is a custom container, get the height from the custom container itself
this.positions.height = (container ? container.clientHeight : document.documentElement.clientHeight);
this.positions.bottom = this.positions.top + this.positions.height;
return this.positions;
}
}
export const viewport = new Viewport();
export { viewport as default };

View File

@@ -0,0 +1,249 @@
import cssTransform from '../helpers/cssTransform';
import isImageLoaded from '../helpers/isImageLoaded';
import { viewport } from '../helpers/viewport';
class ParallaxInstance {
constructor(element, options) {
// set the element & settings
this.element = element;
this.elementContainer = element;
this.settings = options;
this.isVisible = true;
this.isInit = false;
this.oldTranslateValue = -1;
this.init = this.init.bind(this);
// check if images has not been loaded yet
if (isImageLoaded(element)) {
this.init();
} else {
this.element.addEventListener('load', this.init);
}
}
init() {
// for some reason, <picture> are init an infinite time on windows OS
if (this.isInit) return;
// check if element has not been already initialized with simpleParallax
if (this.element.closest('.simpleParallax')) return;
if (this.settings.overflow === false) {
// if overflow option is set to false
// wrap the element into a div to apply overflow
this.wrapElement(this.element);
}
// apply the transform style on the image
this.setTransformCSS();
// get the current element offset
this.getElementOffset();
// init the Intesection Observer
this.intersectionObserver();
// get its translated value
this.getTranslateValue();
// apply its translation even if not visible for the first init
this.animate();
// if a delay has been set
if (this.settings.delay > 0) {
// apply a timeout to avoid buggy effect
setTimeout(() => {
// apply the transition style on the image
this.setTransitionCSS();
}, 10);
}
// for some reason, <picture> are init an infinite time on windows OS
this.isInit = true;
}
// if overflow option is set to false
// wrap the element into a .simpleParallax div and apply overflow hidden to hide the image excedant (result of the scale)
wrapElement() {
// check is current image is in a <picture> tag
const elementToWrap = this.element.closest('picture') || this.element;
// create a .simpleParallax wrapper container
const wrapper = document.createElement('div');
wrapper.classList.add('simpleParallax');
wrapper.style.overflow = 'hidden';
// append the image inside the new wrapper
elementToWrap.parentNode.insertBefore(wrapper, elementToWrap);
wrapper.appendChild(elementToWrap);
this.elementContainer = wrapper;
}
// unwrap the element from .simpleParallax wrapper container
unWrapElement() {
const wrapper = this.elementContainer;
wrapper.replaceWith(...wrapper.childNodes);
}
// apply default style on element
setTransformCSS() {
if (this.settings.overflow === false) {
// if overflow option is set to false
// add scale style so the image can be translated without getting out of its container
this.element.style[cssTransform] = `scale(${this.settings.scale})`;
}
// add will-change CSS property to improve perfomance
this.element.style.willChange = 'transform';
}
// apply the transition effet
setTransitionCSS() {
// add transition option
this.element.style.transition = `transform ${this.settings.delay}s ${this.settings.transition}`;
}
// remove style of the element
unSetStyle() {
// remove will change inline style
this.element.style.willChange = '';
this.element.style[cssTransform] = '';
this.element.style.transition = '';
}
// get the current element offset
getElementOffset() {
// get position of the element
const positions = this.elementContainer.getBoundingClientRect();
// get height
this.elementHeight = positions.height;
// get offset top
this.elementTop = positions.top + viewport.positions.top;
// if there is a custom container
if (this.settings.customContainer) {
// we need to do some calculation to get the position from the parent rather than the viewport
const parentPositions = this.settings.customContainer.getBoundingClientRect();
this.elementTop = (positions.top - parentPositions.top) + viewport.positions.top;
}
// get offset bottom
this.elementBottom = this.elementHeight + this.elementTop;
}
// build the Threshold array to cater change for every pixel scrolled
buildThresholdList() {
const thresholds = [];
for (let i = 1.0; i <= this.elementHeight; i++) {
const ratio = i / this.elementHeight;
thresholds.push(ratio);
}
return thresholds;
}
// create the Intersection Observer
intersectionObserver() {
const options = {
root: null,
threshold: this.buildThresholdList(),
};
this.observer = new IntersectionObserver(this.intersectionObserverCallback.bind(this), options);
this.observer.observe(this.element);
}
// Intersection Observer Callback to set the element at visible state or not
intersectionObserverCallback(entries) {
for (let i = entries.length - 1; i >= 0; i--) {
if (entries[i].isIntersecting) {
this.isVisible = true;
} else {
this.isVisible = false;
}
}
}
// check if the current element is visible in the Viewport
// for browser that not support Intersection Observer API
checkIfVisible() {
return this.elementBottom > viewport.positions.top && this.elementTop < viewport.positions.bottom;
}
// calculate the range between image will be translated
getRangeMax() {
// get the real height of the image without scale
const elementImageHeight = this.element.clientHeight;
// range is calculate with the image height by the scale
this.rangeMax = elementImageHeight * this.settings.scale - elementImageHeight;
}
// get the percentage and the translate value to apply on the element
getTranslateValue() {
// calculate the % position of the element comparing to the viewport
// rounding percentage to a 1 number float to avoid unn unnecessary calculation
let percentage = ((viewport.positions.bottom - this.elementTop) / ((viewport.positions.height + this.elementHeight) / 100)).toFixed(1);
// sometime the percentage exceeds 100 or goes below 0
percentage = Math.min(100, Math.max(0, percentage));
// sometime the same percentage is returned
// if so we don't do aything
if (this.oldPercentage === percentage) {
return false;
}
// if not range max is set, recalculate it
if (!this.rangeMax) {
this.getRangeMax();
}
// transform this % into the max range of the element
// rounding translateValue to a non float int - as minimum pixel for browser to render is 1 (no 0.5)
this.translateValue = ((percentage / 100) * this.rangeMax - this.rangeMax / 2).toFixed(0);
// sometime the same translate value is returned
// if so we don't do aything
if (this.oldTranslateValue === this.translateValue) {
return false;
}
// store the current percentage
this.oldPercentage = percentage;
this.oldTranslateValue = this.translateValue;
return true;
}
// animate the image
animate() {
let translateValueY = 0;
let translateValueX = 0;
let inlineCss;
if (this.settings.orientation.includes('left') || this.settings.orientation.includes('right')) {
// if orientation option is left or right
// use horizontal axe - X axe
translateValueX = `${this.settings.orientation.includes('left') ? this.translateValue * -1 : this.translateValue}px`;
}
if (this.settings.orientation.includes('up') || this.settings.orientation.includes('down')) {
// if orientation option is up or down
// use vertical axe - Y axe
translateValueY = `${this.settings.orientation.includes('up') ? this.translateValue * -1 : this.translateValue}px`;
}
// set style to apply to the element
if (this.settings.overflow === false) {
// if overflow option is set to false
// add the scale style
inlineCss = `translate3d(${translateValueX}, ${translateValueY}, 0) scale(${this.settings.scale})`;
} else {
inlineCss = `translate3d(${translateValueX}, ${translateValueY}, 0)`;
}
// add style on the element using the adequate CSS transform
this.element.style[cssTransform] = inlineCss;
}
}
export default ParallaxInstance;

View File

@@ -0,0 +1,181 @@
import { viewport } from './helpers/viewport';
import convertToArray from './helpers/convertToArray';
import ParallaxInstance from './instances/parallax';
let intersectionObserverAvailable = true;
let isInit = false;
let instances = [];
let instancesLength;
let frameID;
let resizeID;
export default class SimpleParallax {
constructor(elements, options) {
if (!elements) return;
this.elements = convertToArray(elements);
this.defaults = {
delay: 0.4,
orientation: 'up',
scale: 1.3,
overflow: false,
transition: 'cubic-bezier(0,0,0,1)',
customContainer: false,
};
this.settings = Object.assign(this.defaults, options);
// check if the browser handle the Intersection Observer API
if (!('IntersectionObserver' in window)) intersectionObserverAvailable = false;
if (this.settings.customContainer) {
console.log(convertToArray(this.settings.customContainer)[0])
this.customContainer = convertToArray(this.settings.customContainer)[0];
}
this.lastPosition = -1;
this.resizeIsDone = this.resizeIsDone.bind(this);
this.handleResize = this.handleResize.bind(this);
this.proceedRequestAnimationFrame = this.proceedRequestAnimationFrame.bind(this);
this.init();
}
init() {
viewport.setViewportAll(this.customContainer);
for (let i = this.elements.length - 1; i >= 0; i--) {
const instance = new ParallaxInstance(this.elements[i], this.settings);
instances.push(instance);
}
// update the instance length
instancesLength = instances.length;
// only if this is the first simpleParallax init
if (!isInit) {
// init the frame
this.proceedRequestAnimationFrame();
window.addEventListener('resize', this.resizeIsDone);
isInit = true;
}
}
// wait for resize to be completely done
resizeIsDone() {
clearTimeout(resizeID);
resizeID = setTimeout(this.handleResize, 500);
}
// handle the resize process, some coordonates need to be re-calculate
handleResize() {
// re-get all the viewport positions
viewport.setViewportAll(this.customContainer);
for (let i = instancesLength - 1; i >= 0; i--) {
// re-get the current element offset
instances[i].getElementOffset();
// re-get the range if the current element
instances[i].getRangeMax();
}
// force the request animation frame to fired
this.lastPosition = -1;
}
// animation frame
proceedRequestAnimationFrame() {
// get the offset top of the viewport
viewport.setViewportTop(this.customContainer);
if (this.lastPosition === viewport.positions.top) {
// if last position if the same than the curent one
// callback the animationFrame and exit the current loop
frameID = window.requestAnimationFrame(this.proceedRequestAnimationFrame);
return;
}
// get the offset bottom of the viewport
viewport.setViewportBottom();
// proceed with the current element
for (let i = instancesLength - 1; i >= 0; i--) {
this.proceedElement(instances[i]);
}
// callback the animationFrame
frameID = window.requestAnimationFrame(this.proceedRequestAnimationFrame);
// store the last position
this.lastPosition = viewport.positions.top;
}
// proceed the element
proceedElement(instance) {
let isVisible = false;
// is not support for Intersection Observer API
// or if this is a custom container
// use old function to check if element visible
if (!intersectionObserverAvailable || this.customContainer) {
isVisible = instance.checkIfVisible();
// if support
// use response from Intersection Observer API Callback
} else {
isVisible = instance.isVisible;
}
// if element not visible, stop it
if (!isVisible) return;
// if percentage is equal to the last one, no need to continue
if (!instance.getTranslateValue()) {
return;
}
// animate the image
instance.animate();
}
destroy() {
const instancesToDestroy = [];
// remove all instances that need to be destroyed from the instances array
instances = instances.filter((instance) => {
if (this.elements.includes(instance.element)) {
// push instance that need to be destroyed into instancesToDestroy
instancesToDestroy.push(instance);
return false;
}
return instance;
});
for (let i = instancesToDestroy.length - 1; i >= 0; i--) {
// unset style
instancesToDestroy[i].unSetStyle();
if (this.settings.overflow === false) {
// if overflow option is set to false
// unwrap the element from .simpleParallax wrapper container
instancesToDestroy[i].unWrapElement();
}
}
// update the instance length var
instancesLength = instances.length;
// if no instances left, remove the raf and resize event = simpleParallax fully destroyed
if (!instancesLength) {
// cancel the animation frame
window.cancelAnimationFrame(frameID);
// detach the resize event
window.removeEventListener('resize', this.handleResize);
}
}
}