static/js/com/gif-player/gif-player.js (141 lines of code) (raw):

import $ from 'jquery'; import './gif-player.scss' /** * List of tags strings. * @const * @enum {String} */ const TAGS = { img: 'img', picture: 'picture', source: 'source' }; /** * List of attributes strings. * @const * @enum {String} */ const ATTRIBUTES = { src: 'src', srcSet: 'srcset', gifSrc: 'data-gif-src', gifPlaceholder: 'data-gif-placeholder', media: 'media' }; /** * List of class-names values. * @const * @enum {String} */ const CLASSES = { player: 'gif-player', playerShowMod: '_show-gif', playerActiveMod: '_active', image: 'gif-player__image', imageActiveMod: '_active', imageLoadingMod: '_loading' }; // Empty array for storing instances GifPlayer.instances = []; /** * Class representing GifPlayer. * @param {String|HTMLElement} element Selector or node of the image tag * * @constructor * @property {jQuery} $player * @property {jQuery} $image * @property {Function} clickHandler */ function GifPlayer(element) { if (!(this instanceof GifPlayer)) return new GifPlayer(element); // Search for the element const $element = $(element, 'body'); if ($element === 0) throw new Error('Mount node for component not found.'); else if ($element.length > 1) throw new Error('Component can be attached only to 1 node. ' + $element.length + ' nodes given.'); //Store element node this.$element = $element; this.elementIsImage = this.$element.is(TAGS.img); // Get gif source const gifSrc = $element.attr(ATTRIBUTES.gifSrc); if (this.elementIsImage && !gifSrc) throw new Error('Can\'t init Gif-player, please provide ' + ATTRIBUTES.gifSrc + ' attribute for ' + element + '.'); // Store image node this.$image = this.elementIsImage ? $element : $(element).find(TAGS.img); // Init and store player wrapper this.$player = this._initPlayerWrapper(); //init breakpoints behavior if (!this.elementIsImage) this._initBreakpointsBehavior(); // Init and store click handler to be able to remove it on destroy this.clickHandler = this._initClickHandler(); // Listen for clicks and call toggle play function this.$player.on('click', this.clickHandler); // Store this instance GifPlayer._addInstance(this); } /** * Add instance of GifPlayer to instances array. * @param {GifPlayer} instance * @static * @private */ GifPlayer._addInstance = function (instance) { GifPlayer.instances.push(instance); }; /** * Remove instance of GifPlayer from instances array. * @param {GifPlayer} instance * @static */ GifPlayer._removeInstance = function (instance) { const index = GifPlayer.instances.indexOf(instance); if (index > -1) { GifPlayer.instances.splice(index, 1); } }; /** * Destroy all instances of GifPlayer. * @static */ GifPlayer.destroyAll = function () { GifPlayer.instances.forEach(function (instance) { instance.destroy(); }); GifPlayer.instances = []; }; /** * Wrap an original image with a player node. * @private * @return {jQuery} */ GifPlayer.prototype._initPlayerWrapper = function () { const $image = this.$image; const $player = $('<div/>'); // Adding class-names $player .addClass(CLASSES.player) .addClass(CLASSES.playerShowMod); // Wrap image with player node $image.wrap($player); return $image.parent(); }; /** * Init breapoints behavior for media queries */ GifPlayer.prototype._initBreakpointsBehavior = function () { this.defaultSrc = this.$image.attr(ATTRIBUTES.src); this.defaultGifSrc = this.$image.attr(ATTRIBUTES.gifSrc); if (this.$element.is(TAGS.picture)) { //find all media queries for this player instance this.mediaQueries = []; this.$element.find(TAGS.source).each((idx, source) => { this.mediaQueries.push($(source).attr('media')); }); //add listeners for media queries this.mediaQueries.forEach((query) => { let currentQuery = window.matchMedia(query); this.setImagesForBreakpoint(currentQuery); currentQuery.addListener(function (e) { this.setImagesForBreakpoint(e); }.bind(this)); }); } }; /** * Init click handler with saved links for the player and for the image node. * @private * @return {Function} */ GifPlayer.prototype._initClickHandler = function () { const gifPlayer = this; const $image = this.$image; return function () { let isActive = $image.hasClass(CLASSES.imageActiveMod); if (!isActive) { gifPlayer.play(); } else { gifPlayer.stop(); } } }; /** * Start playing the gif. */ GifPlayer.prototype.play = function () { const $player = this.$player; const $image = this.$image; const gifSrc = $image.attr(ATTRIBUTES.gifSrc); const placeholder = $image.attr(ATTRIBUTES.src); // Set active player class $player.addClass(CLASSES.playerActiveMod); // Set image active and loading modifiers $image .addClass(CLASSES.imageActiveMod) .addClass(CLASSES.imageLoadingMod); // Store placeholder source in data-attribute $image.attr(ATTRIBUTES.gifPlaceholder, placeholder); // Load gif into image $image.attr(ATTRIBUTES.src, gifSrc); // Remove loading modifier if gif is loaded $image.load(function () { $image.removeClass(CLASSES.imageLoadingMod); }) }; /** * Stop playing the gif. */ GifPlayer.prototype.stop = function () { const $player = this.$player; const $image = this.$image; const placeholder = $image.attr(ATTRIBUTES.gifPlaceholder); // Remove player active modifier $player.removeClass(CLASSES.playerActiveMod); // Remove image active modifier $image.removeClass(CLASSES.imageActiveMod); // Replace gif source with the placeholder $image.attr(ATTRIBUTES.src, placeholder); }; /** * Destroy function that removes click event handler from player node. */ GifPlayer.prototype.destroy = function () { this.stop(); this.$player.off('click', this.clickHandler); GifPlayer._removeInstance(this); }; /** * Set images for GifPlayer depending on media query */ GifPlayer.prototype.setImagesForBreakpoint = function () { const sourceToMatch = this._findSourceMatch(); const srcToSet = sourceToMatch.length > 0 ? sourceToMatch.attr(ATTRIBUTES.srcSet) : this.defaultSrc; const gifSrcToSet = sourceToMatch.length > 0 ? sourceToMatch.attr(ATTRIBUTES.gifSrc) : this.defaultGifSrc; const attrToSetSrc = this.$image.hasClass(CLASSES.imageActiveMod) ? ATTRIBUTES.gifPlaceholder : ATTRIBUTES.src; this.$image.attr(ATTRIBUTES.gifSrc, gifSrcToSet); this.$image.attr(attrToSetSrc, srcToSet); }; /** * Find source tag for matching media query if exists * @private * @return {Array} */ GifPlayer.prototype._findSourceMatch = function () { const currentQuery = this.mediaQueries.map(function (query) { return window.matchMedia(query); }).filter(function (query) { return query.matches === true; }); return currentQuery.length > 0 ? this.$element.find(TAGS.source).filter(function () { return $(this).attr(ATTRIBUTES.media) === currentQuery[0].media }) : []; }; export default GifPlayer;