Skip to content

Instantly share code, notes, and snippets.

@derekshirk
Created August 26, 2019 18:10
Show Gist options
  • Save derekshirk/0e4c7abd7b31e19a128e66ea703b0dc7 to your computer and use it in GitHub Desktop.
Save derekshirk/0e4c7abd7b31e19a128e66ea703b0dc7 to your computer and use it in GitHub Desktop.
Image Swapping with data attributes
import $ from 'jquery';
const NAME = 'swapImage';
const DATA_KEY = `decathlon.${NAME}`;
const EVENT_KEY = `.${DATA_KEY}`;
const ClassName = {
CURRENT: 'is-current',
LOADING: 'is-loading'
};
const EventName = {
LOAD: `load${EVENT_KEY}`
};
const AttrName = {
ALT: 'data-swap-image-alt',
SIZES: 'data-swap-image-sizes',
SRC: 'data-swap-image-src',
SRCSET: 'data-swap-image-srcset',
TARGET: 'data-swap-image-target',
ZOOM: 'data-swap-image-zoom'
};
/**
* Class representing an image or image container that will have its src
* (and other related attributes) swapped.
*/
export default class SwapImage {
/**
* Create a SwapImage. Does not actually swap any images until explicitly
* asked to do so.
*
* @param {*} element - A valid jQuery element or selector, either an `<img>`
* element or a close parent.
*/
constructor (element) {
this._element = $(element);
this._elementId = this._element.attr('id');
this._imageElement = this._element.is('img') ?
this._element : this._element.find('img');
this._originalAttr = this._lastAttr = this._currentAttr = {
src: this._imageElement.attr('src'),
alt: this._imageElement.attr('alt') || '',
srcset: this._imageElement.attr('srcset'),
sizes: this._imageElement.attr('sizes'),
'data-zoom-src': this._imageElement.attr('data-zoom-src')
};
}
/**
* Swap the current image attributes for new ones. The attributes will only
* swap once the new image assets have loaded. Prior to that event, the target
* element will be given a class of `is-loaded` which can be used to style
* the appearance in this state.
*
* @param {Object} [attr] - The attributes to replace, for example `src`,
* `alt`, `srcset` and/or `sizes`.
*/
swap (attr = {}) {
attr = $.extend({}, {
src: '',
alt: ''
}, attr);
if (attr === this._currentAttr) {
return;
}
if (this._preloadElement) {
this._preloadElement.remove();
}
this._preloadElement = $('<img/>').one(EventName.LOAD, () => {
this._element.removeClass(ClassName.LOADING);
this._imageElement.attr(attr);
});
this._element.addClass(ClassName.LOADING);
this._preloadElement.attr(attr);
if (this._elementId) {
$(`[${AttrName.TARGET}="${this._elementId}"]`)
.removeClass(ClassName.CURRENT)
.filter(`[${AttrName.SRC}="${attr.src}"]`)
.addClass(ClassName.CURRENT);
}
this._lastAttr = this._currentAttr;
this._currentAttr = attr;
}
/**
* Revert the image asset's attributes to what they were originally.
*/
revert () {
return this.swap(this._originalAttr);
}
/**
* Undo the last image swap only. This is preferable to revert when multiple
* swaps may happen in sequence, for example alternate angles in an image
* gallery.
*/
undo () {
return this.swap(this._lastAttr);
}
/**
* A jQuery interface to enable usage of this class as a plugin. Checks for
* an existing class instance and only creates a new one if it isn't present.
*
* @static
* @protected
* @param {(Object|String)} [attr] - Either attributes to pass directly to the
* `swap` method, a string representing the name of a different method, or a
* string representing a `src` value to swap.
* @returns {jQuery}
* @example
* // Swap one image for another
* $(img).swapImage('example/image.jpg');
* // Swap more attributes
* $(img).swapImage({
* src: 'example/image.jpg',
* alt: 'Accessibility is important',
* srcset: '...',
* sizes: '...'
* });
* // Undo the most recent swap
* $(img).swapImage('undo');
* // Revert to what it was before we swapped anything
* $(img).swapImage('revert');
*/
static _jQueryInterface (attr) {
return this.each(function () {
const $this = $(this);
let data = $this.data(DATA_KEY);
// If this is a new instance, we instantiate and attach to both the
// called element and its associated image. This makes the swap easier
// to manipulate from other plugins and scripts without losing state.
if (!data) {
data = new SwapImage(this);
$this.add(data._imageElement).data(DATA_KEY, data);
}
if (attr) {
if (typeof attr === 'string') {
if (data[attr] === undefined) {
attr = {
src: attr
};
} else {
return data[attr]();
}
}
data.swap(attr);
}
});
}
/**
* A function to call from an event that will trigger a swap from a different
* element. Used to enable a Bootstrap-style delegated data API.
*
* @static
* @protected
* @param {*} triggerElement - A valid jQuery element or selector representing
* whatever element triggered the event.
* @param {String} [revertOn] - Optional event to revert the change on.
* @returns {jQuery}
*/
static _dataApiAction (triggerElement, revertOn) {
triggerElement = $(triggerElement);
const attr = {
src: triggerElement.attr(AttrName.SRC),
alt: triggerElement.attr(AttrName.ALT),
srcset: triggerElement.attr(AttrName.SRCSET),
sizes: triggerElement.attr(AttrName.SIZES),
'data-zoom-src': triggerElement.attr(AttrName.ZOOM)
};
const targetId = triggerElement.attr(AttrName.TARGET);
let targetElement = triggerElement;
if (targetId) {
targetElement = $(`#${targetId}`);
}
if (revertOn) {
targetElement.one(revertOn, () => {
targetElement.swapImage('revert');
});
}
targetElement.swapImage(attr);
}
}
$.fn[NAME] = SwapImage._jQueryInterface;
/**
* SwapImage Data API
*
* Makes image-swap effects for product tiles and hover-based previews easier to
* implement within templates.
*/
$(document)
.on('click.decathlon', '[data-swap-image-on="click"]', function (event) {
event.preventDefault();
SwapImage._dataApiAction(this);
})
.on('mouseover.decathlon', '[data-swap-image-on="hover"]', function (event) {
SwapImage._dataApiAction(this, 'mouseout.decathlon');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment