UNPKG

scroll-parallax

Version:

ES6/ES2015 unobstrusive modern HW accelerated scrollable images parallax

153 lines (136 loc) 4.22 kB
/** * This class will manage the parallax container */ import { prefix } from './helpers/helpers' import o from 'riot-observable' /** * Check the translate3d feature */ const TRANSFORM_PREFIX = (function (div) { return prefix(div.style, 'transform') })(document.createElement('div')) const HAS_MATRIX = (function (div) { div.style[TRANSFORM_PREFIX] = 'matrix(1, 0, 0, 1, 0, 0)' return /matrix/g.test(div.style.cssText) })(document.createElement('div')) export default class Canvas { constructor(element, opts) { // make this object observable o(this) this.opts = opts this.element = element this.wrapper = element.parentNode this.isLoaded = false // store the initial element properties - deep clone this.initial = element.cloneNode(true) } /** * Load the element * @returns { Object } - Canvas */ load() { const isImage = this.element.complete !== undefined const isVideo = this.element.oncanplay !== undefined if (isImage && !this.element.complete) { this.element.onload = () => this.onElementLoaded() } else if (isVideo && !this.element.oncanplay) { this.element.onload = this.element.oncanplay = () => this.onElementLoaded() } else { this.onElementLoaded() } return this } destroy() { this.element.parentNode.replaceChild(this.initial, this.element) this.off('*') } /** * Callback triggered when the element gets loaded * @returns { Object } - Canvas */ onElementLoaded() { this.isLoaded = true this.update() this.element.style.willChange = 'transform' this.trigger('loaded', this.element) return this } /** * Center the element in its wrapper * @returns { Object } - Canvas */ update() { const iw = this.element.naturalWidth || this.element.width || this.element.offsetWidth, ih = this.element.naturalHeight || this.element.height || this.element.offsetHeight, ratio = iw / ih, size = this.size let nh, nw, offsetTop, offsetLeft // calculate the new width and the height // keeping the element ratio if (size.width / ratio <= size.height) { nw = size.height * ratio nh = size.height } else { nw = size.width nh = size.width / ratio } // zoom the element if necessary if (nh <= size.height + size.height * this.opts.safeHeight) { nw += nw * this.opts.safeHeight nh += nh * this.opts.safeHeight } // calculate the offset top/left rounding it offsetTop = -~~((nh - size.height) / 2) offsetLeft = -~~((nw - size.width) / 2) this.element.width = nw this.element.height = nh this.element.style.top = `${offsetTop}px` this.element.style.left = `${offsetLeft}px` return this } /** * Draw the element on the canvas * @returns { Object } - Canvas */ draw({ scrollTop, width, height }) { const size = this.size, // this value will be: // < 0 when the element is on the top // 0 when the element is in the center of the screen // > 0 when the element is at the bottom perc = (this.offset.top + size.height * this.opts.center + height / 2 - scrollTop) / height - 1, // increase the percentage effect according to the intensity // and the current element height offset = ~~(perc * (this.element.height / size.height / 2 * this.opts.intensity) * 10) this.element.style[TRANSFORM_PREFIX] = HAS_MATRIX ? `matrix(1,0,0,1, 0, ${-offset})` : `translate(0, ${-offset}px)` return this } /** * Get the parent wrapper bounds * @returns { Object } - parent tag bounds properties */ get bounds() { return this.wrapper.getBoundingClientRect() } /** * Get the parent wrapper offset * @returns { Object } - top and left position of the element parent tag */ get offset() { return { top: this.wrapper.offsetTop, left: this.wrapper.offsetLeft } } /** * Get the parent wrapper size * @returns { Object } - the height and the width of the element parent tag */ get size() { const props = this.bounds return { height: props.height | 0, width: props.width | 0 } } }