Comparing version 0.0.0 to 1.0.0
/*! | ||
* palettify v0.0.0 | ||
* palettify v1.0.0 | ||
* (c) 2017 Dobromir Hristov | ||
@@ -40,137 +40,376 @@ * Released under the MIT License. | ||
/** | ||
* palettify | ||
* @param {Object} opts | ||
* @param {String | NodeList} opts.imageTarget | ||
* @param {String | NodeList} opts.hoverTarget | ||
* @param {String} opts.attachBoxShadowTo | ||
* @param {String | Number} opts.opacity | ||
* @param {String} opts.opacitySecondary | ||
* @param {String} opts.colorIndexToUse | ||
* @param {String} opts.boxShadowTemplate - Provide a boxShadow template to apply. '0 2px 2px {color}, 3px 3px {colorSecondary}' | ||
* @callback ResolverFn | ||
* @param {string} varName - variable name before being parsed. | ||
* For example: {a.b.c} -> 'a.b.c', { x } -> 'x' | ||
* @param {Object} view - the view object that was passed to .render() function | ||
* @returns {string|number|boolean|Object|undefined} the value to be | ||
* interpolated. If the function returns undefined, the value resolution | ||
* algorithm will go ahead with the default behaviour (resolving the | ||
* variable name from the provided object). | ||
*/ | ||
function palettify () { | ||
/** | ||
* Extract the colors from image tag or Background-image inline style | ||
* @param {HTMLElement} paletteTarget - The palette target to get the colors form | ||
* @return {Array} Returns an object with RGB colors | ||
* @private | ||
*/ | ||
function __extractColors (paletteTarget) { | ||
var image = paletteTarget; | ||
if (!paletteTarget) { throw Error('Target is not an element', paletteTarget) } | ||
// Our sample is not a img tag so we try to get its background image. | ||
if (paletteTarget.tagName !== 'IMG' && paletteTarget.style.backgroundImage) { Error('Tag provided is not an image and does not have a background-image style attached to it.'); } | ||
// Its not an IMG tag so we try to crate one under the hood. | ||
if (paletteTarget.tagName !== 'IMG') { | ||
image = new Image(paletteTarget.offsetWidth, paletteTarget.offsetHeight); | ||
image.src = paletteTarget.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, ''); | ||
var VAR_MATCH_REGEX = /\{\{\s*(.*?)\s*\}\}/g; | ||
function _valueToString (value) { | ||
switch (typeof value) { | ||
case 'string': | ||
case 'number': | ||
case 'boolean': | ||
return value; | ||
case 'object': | ||
try { | ||
// null is an object but is falsy. Swallow it. | ||
return value === null ? '' : JSON.stringify(value); | ||
} catch (jsonError) { | ||
return '{...}'; | ||
} | ||
return new colorThief_min().getPalette(image, 3) | ||
default: | ||
// Anything else will be replaced with an empty string | ||
// For example: undefined, Symbol, etc. | ||
return ''; | ||
} | ||
} | ||
/** | ||
* Returns the rgba color from current color with applied opacity | ||
* @param {Array} color | ||
* @param {String | Number} opacity | ||
* @return {string} | ||
* @private | ||
*/ | ||
function __getRgbaColor (color, opacity) { | ||
var | ||
r = color[0], | ||
g = color[1], | ||
b = color[2]; | ||
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")") | ||
/** | ||
* Recursively goes through an object trying to resolve a path. | ||
* | ||
* @param {Object} scope - The object to traverse (in each recursive call we dig into this object) | ||
* @param {string[]} path - An array of property names to traverse one-by-one | ||
* @param {number} [pathIndex=0] - The current index in the path array | ||
*/ | ||
function _recursivePathResolver(scope, path, pathIndex) { | ||
if ( pathIndex === void 0 ) pathIndex = 0; | ||
if (typeof scope !== 'object' || scope === null || scope === undefined) { | ||
return ''; | ||
} | ||
/** | ||
* Adds boxShadow to an element. | ||
* Required the color and the element to attach to | ||
* @param {Array} colors - Array of RGB Colors | ||
* @param {Object} obj - Object that holds the hoverTarget and image | ||
* @param {HTMLElement} obj.hoverTarget - Reference to the hoverTarget dom element | ||
* @param {HTMLElement} obj.image - Reference to the image dom element | ||
* @param {Array} obj.colors - Array of RGB Colors | ||
* @param {String} obj.rgbaColor - rgba transformed color with opacity in mind. | ||
* @param {String} obj.rgbaColorSecondary - rgba transformed color with secondary opacity in mind. | ||
* @private | ||
*/ | ||
function __addBoxShadowToElementData (colors, obj) { | ||
var ref = self.options; | ||
var opacity = ref.opacity; | ||
var opacitySecondary = ref.opacitySecondary; | ||
var boxShadowTemplate = ref.boxShadowTemplate; | ||
var color = colors[self.options.colorIndexToUse], | ||
rgbaColor = __getRgbaColor(color, opacity), | ||
rgbaColorSecondary = __getRgbaColor(color, opacitySecondary); | ||
var boxShadow; | ||
if (!boxShadowTemplate) { | ||
boxShadow = "0 2px 2px " + rgbaColor + ", 0 4px 4px " + rgbaColor + ", 0 8px 8px " + rgbaColor + ", 0 16px 16px " + rgbaColor + ", 0 32px 32px " + rgbaColorSecondary + ", 0 64px 64px " + rgbaColorSecondary; | ||
var varName = path[pathIndex]; | ||
var value = scope[varName]; | ||
if (pathIndex === path.length - 1) { | ||
// It's a leaf, return whatever it is | ||
return value; | ||
} | ||
return _recursivePathResolver(value, path, ++pathIndex); | ||
} | ||
function defaultResolver(varName, view) { | ||
return _recursivePathResolver(view, varName.split('.')); | ||
} | ||
/** | ||
* Replaces every {{variable}} inside the template with values provided by view. | ||
* | ||
* @param {string} template - The template containing one or more {{variableNames}} every variable | ||
* names that is used in the template. If it's omitted, it'll be assumed an empty object. | ||
* @param {Object} [view={}] - An object containing values for every variable names that is used in | ||
* the template. If it's omitted, it'll be set to an empty object essentially removing all | ||
* {{varName}}s in the template. | ||
* @param {ResolverFn} [resolver] - An optional function that will be | ||
* called for every {{varName}} to generate a value. If the resolver throws an error | ||
* we'll proceed with the default value resolution algorithm (find the value from the view | ||
* object). | ||
* @returns {string} - Template where its variable names replaced with | ||
* corresponding values. If a value is not found or is invalid, it will | ||
* be assumed empty string ''. If the value is an object itself, it'll | ||
* be stringified by JSON. | ||
* In case of a JSON stringify error the result will look like "{...}". | ||
*/ | ||
function render (template, view, resolver) { | ||
if ( view === void 0 ) view = {}; | ||
if ( resolver === void 0 ) resolver = defaultResolver; | ||
// don't touch the template if it is not a string | ||
if (typeof template !== 'string') { | ||
return template; | ||
} | ||
return template.replace(VAR_MATCH_REGEX, function (match, varName) { | ||
try { | ||
// defaultResolver never throws | ||
return _valueToString(resolver(varName, view)); | ||
} catch (e) { | ||
// if your resolver throws, we proceed with the default resolver | ||
return _valueToString(defaultResolver(varName, view)); | ||
} | ||
}); | ||
} | ||
var render_1$1 = render; | ||
var render_1 = render_1$1; | ||
var index$2 = function isMergeableObject(value) { | ||
return isNonNullObject(value) && isNotSpecial(value) | ||
}; | ||
function isNonNullObject(value) { | ||
return !!value && typeof value === 'object' | ||
} | ||
function isNotSpecial(value) { | ||
var stringValue = Object.prototype.toString.call(value); | ||
return stringValue !== '[object RegExp]' | ||
&& stringValue !== '[object Date]' | ||
} | ||
function emptyTarget(val) { | ||
return Array.isArray(val) ? [] : {} | ||
} | ||
function cloneIfNecessary(value, optionsArgument) { | ||
var clone = optionsArgument && optionsArgument.clone === true; | ||
return (clone && index$2(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value | ||
} | ||
function defaultArrayMerge(target, source, optionsArgument) { | ||
var destination = target.slice(); | ||
source.forEach(function(e, i) { | ||
if (typeof destination[i] === 'undefined') { | ||
destination[i] = cloneIfNecessary(e, optionsArgument); | ||
} else if (index$2(e)) { | ||
destination[i] = deepmerge(target[i], e, optionsArgument); | ||
} else if (target.indexOf(e) === -1) { | ||
destination.push(cloneIfNecessary(e, optionsArgument)); | ||
} | ||
}); | ||
return destination | ||
} | ||
function mergeObject(target, source, optionsArgument) { | ||
var destination = {}; | ||
if (index$2(target)) { | ||
Object.keys(target).forEach(function(key) { | ||
destination[key] = cloneIfNecessary(target[key], optionsArgument); | ||
}); | ||
} | ||
Object.keys(source).forEach(function(key) { | ||
if (!index$2(source[key]) || !target[key]) { | ||
destination[key] = cloneIfNecessary(source[key], optionsArgument); | ||
} else { | ||
destination[key] = deepmerge(target[key], source[key], optionsArgument); | ||
} | ||
}); | ||
return destination | ||
} | ||
function deepmerge(target, source, optionsArgument) { | ||
var array = Array.isArray(source); | ||
var options = optionsArgument || { arrayMerge: defaultArrayMerge }; | ||
var arrayMerge = options.arrayMerge || defaultArrayMerge; | ||
if (array) { | ||
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument) | ||
} else { | ||
boxShadow = boxShadowTemplate.replace('{color}', rgbaColor).replace('{colorSecondary}', rgbaColorSecondary); | ||
return mergeObject(target, source, optionsArgument) | ||
} | ||
obj.colors = colors; | ||
obj.rgbaColor = rgbaColor; | ||
obj.rgbaColorSecondary = rgbaColorSecondary; | ||
obj.boxShadow = boxShadow; | ||
} | ||
} | ||
var isInitialized = false; | ||
deepmerge.all = function deepmergeAll(array, optionsArgument) { | ||
if (!Array.isArray(array) || array.length < 2) { | ||
throw new Error('first argument should be an array with at least two elements') | ||
} | ||
// we are sure there are at least 2 values, so it is safe to have no initial value | ||
return array.reduce(function(prev, next) { | ||
return deepmerge(prev, next, optionsArgument) | ||
}) | ||
}; | ||
var index$2$1 = deepmerge; | ||
/** | ||
* Creates a palettify instance | ||
* @module palettify | ||
* @return palettify | ||
*/ | ||
function createPalettify () { | ||
var | ||
isInitialized = false, | ||
__selector = null; | ||
/** | ||
* @typedef {Object} paletteObj | ||
* @property {HTMLElement} eventTarget | ||
* @property {HTMLElement} styleTarget | ||
* @property {HTMLElement} image | ||
* @property {PaletteObject} palette | ||
* @property {Function} enterHandler | ||
* @property {Function} leaveHandler | ||
*/ | ||
/** | ||
* @typedef {Object} PaletteObject | ||
* @property {Array} original | ||
* @property {Array} rgb | ||
* @property {Array} rgba | ||
*/ | ||
var | ||
/** | ||
* Default options to extend | ||
* @typedef {Object} DefaultOptions | ||
* @property {String|HTMLElement} selector - The element that holds all your images and eventTargets. | ||
* @property {String} eventTarget - The event target to attach event listeners to | ||
* @property {String} image - The image to sample | ||
* @property {String} styleTarget - The element to apply styling to. Defaults to image | ||
* @property {Object} contrastColors - Light and Dark colors, based on brightness of each color in the palette | ||
* @property {String} contrastColors.light - Light inverted color | ||
* @property {String} contrastColors.dark - Dark inverted color | ||
* @property {String} activeClass - CSS class to apply on each enterEvent | ||
* @property {String} readyClass - CSS class to apply when palettify is ready | ||
* @property {Number} colorsToExtract - Colors to extract | ||
* @property {String | Array} enterEvent - Event or Array of events to apply listeners to for each enterCallback | ||
* @property {string | Array} leaveEvent - Event or Array of events to apply listeners to for each leaveCallback | ||
* @property {Object} styles - Collection of static and dynamic styles | ||
* @property {Array<number>} styles.opacities - Array of opacities | ||
* @property {Object} styles.static - Object containing valid css styles to apply to styleTarget on ready | ||
* @property {Object} styles.dynamic - Object containing valid css styles to apply to styleTarget on each enterEvent | ||
* @property {Function} beforeEnterCallback - Callback called before the enter event | ||
* @property {Function} afterEnterCallback - Callback called after the enter event | ||
* @property {Function} beforeLeaveCallback - Callback called before the leave event | ||
* @property {Function} afterLeaveCallback - Callback called after the enter event | ||
* @property {Function} onReadyCallback - Callback called after palettify is ready. | ||
*/ | ||
__defaults = { | ||
hoverTarget: Error('Please provide hoverTarget'), | ||
imageTarget: Error('Please provide ImageSrc'), | ||
attachBoxShadowTo: null, | ||
opacity: 0.2, | ||
opacitySecondary: 0.2, | ||
hoverClass: 'box-shadow-attached', | ||
colorIndexToUse: 0, | ||
selector: Error('Please provide а selector in your options.'), | ||
eventTarget: Error('Please provide an eventTarget as a parent for your image in the options.'), | ||
image: Error('Please provide an image to sample.'), | ||
styleTarget: null, | ||
contrastColors: { | ||
light: '#fff', | ||
dark: '#000' | ||
}, | ||
activeClass: 'palettify--active', | ||
readyClass: 'palettify--ready', | ||
colorsToExtract: 3, | ||
enterEvent: 'mouseenter', | ||
leaveEvent: 'mouseleave', | ||
boxShadowTemplate: '' | ||
styles: { | ||
opacities: [0.5, 0.5, 0.5], | ||
static: {}, | ||
dynamic: {} | ||
}, | ||
staticCallback: null, | ||
beforeEnterCallback: null, | ||
afterEnterCallback: null, | ||
beforeLeaveCallback: null, | ||
afterLeaveCallback: null, | ||
onReadyCallback: null | ||
}, | ||
/** | ||
* @typedef {Object} palettify | ||
* @type Object | ||
* @property {DefaultOptions} options | ||
* @property {Array} data | ||
* @property {function} collectElements | ||
* @property {function} extractColorsAndAttachStyles | ||
* @property {function} generateEnterHandler | ||
* @property {function} generateLeaveHandler | ||
* @property {function} attachEventListeners | ||
* @property {function} detachEventListeners | ||
* @property {function} init | ||
* @property {function} destroy | ||
* @property {function} reInit | ||
* @property {function} cleanUp | ||
* @property {function} setOptions | ||
* @property {function} isInitialized | ||
*/ | ||
self = { | ||
/** | ||
* Palettify options | ||
* @name palettify#options | ||
* @type DefaultOptions | ||
*/ | ||
options: {}, | ||
elements: [], | ||
/** | ||
* Gather all needed elements and push into an the `elements` array | ||
* Holds a collection of {@link paletteObj} objects. | ||
* @type Array<paletteObj> | ||
* @name palettify#data | ||
*/ | ||
data: [], | ||
/** | ||
* Gather all needed elements and push into an the {@see palettify#data} | ||
* @type function | ||
* @name palettify#collectElements | ||
*/ | ||
collectElements: function collectElements () { | ||
var hoverTargetsCollection = self.options.hoverTarget; | ||
if (typeof hoverTargetsCollection === 'string') { | ||
hoverTargetsCollection = document.querySelectorAll(hoverTargetsCollection); | ||
} | ||
if (!hoverTargetsCollection.length) { return } | ||
for (var i = 0; i < hoverTargetsCollection.length; i++) { | ||
var | ||
eventTargetsCollection = ''; | ||
__selector = typeof self.options.selector === 'string' ? document.querySelector(self.options.selector) : self.options.selector; | ||
if (!__selector) { throw new Error('Selector ' + self.options.selector + ' does not exist') } | ||
eventTargetsCollection = __selector.querySelectorAll(self.options.eventTarget); | ||
[].slice.call(eventTargetsCollection, 0).forEach(function (eventTarget) { | ||
var | ||
element = hoverTargetsCollection[i], | ||
image = element.querySelector(self.options.imageTarget), | ||
image = eventTarget.querySelector(self.options.image), | ||
styleTarget = self.options.styleTarget === true ? eventTarget : eventTarget.querySelector(self.options.styleTarget || self.options.image), | ||
// Create the main object it self. | ||
obj = { | ||
hoverTarget: element, | ||
image: image | ||
eventTarget: eventTarget, | ||
styleTarget: styleTarget, | ||
image: image, | ||
palette: { | ||
original: [], | ||
rgb: [], | ||
rgba: [] | ||
} | ||
}; | ||
self.elements.push(obj); | ||
} | ||
self.data.push(obj); | ||
}); | ||
}, | ||
/** | ||
* Attaches boxShadow to the collection of Images | ||
* Extracts colors and attaches static styles to each styleTarget {@see palettify#options.styleTarget} | ||
* Adds ready class and calls onReadyCallback when done | ||
* @function | ||
* @name palettify#extractColorsAndAttachStyles | ||
*/ | ||
attachBoxShadowToCollection: function attachBoxShadowToCollection () { | ||
self.elements.forEach(function (obj) { | ||
var color = __extractColors(obj.image); | ||
__addBoxShadowToElementData(color, obj); | ||
extractColorsAndAttachStyles: function extractColorsAndAttachStyles (skipCallbacks) { | ||
if ( skipCallbacks === void 0 ) skipCallbacks = false; | ||
var promises = []; | ||
self.data.forEach(function (obj, index) { | ||
promises[index] = __extractColors(obj.image, self.options.colorsToExtract).then(function (colors) { | ||
obj.palette.original = colors; | ||
obj.palette.rgb = __opacifyPalette(obj.palette.original, []); | ||
obj.palette.rgba = __opacifyPalette(obj.palette.original, self.options.styles.opacities); | ||
obj.palette.contrastColors = __getInvertedColors(obj.palette.original, self.options.contrastColors); | ||
__attachStylesToElement({ | ||
target: obj.styleTarget, | ||
styles: self.options.styles.static, | ||
palette: obj.palette, | ||
staticCallback: self.options.staticCallback, | ||
obj: obj | ||
}); | ||
}); | ||
}); | ||
if (!skipCallbacks) { | ||
Promise.all(promises).then(function (values) { | ||
__selector.classList.add(self.options.readyClass); | ||
typeof self.options.onReadyCallback === 'function' && self.options.onReadyCallback.call(self, self); | ||
}); | ||
} | ||
}, | ||
/** | ||
* Enter event listener callback | ||
* Adds the boxShadow to the target | ||
* @param event | ||
* Generates the enter event listener callback | ||
* Attaches dynamicStyles {@see palettify#options.styles.dynamic} to styleTarget | ||
* @function | ||
* @param {paletteObj} obj | ||
* @name palettify#generateEnterHandler | ||
* @return function | ||
*/ | ||
enterHandler: function enterHandler (obj) { | ||
generateEnterHandler: function generateEnterHandler (obj) { | ||
return function (event) { | ||
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget); | ||
if (target) { | ||
target.classList.add(self.options.hoverClass); | ||
target.style.boxShadow = obj.boxShadow; | ||
if (obj.styleTarget) { | ||
if (typeof self.options.beforeEnterCallback === 'function') { self.options.beforeEnterCallback.call(obj.styleTarget, obj, self.options, event); } | ||
obj.styleTarget.classList.add(self.options.activeClass); | ||
__attachStylesToElement({ | ||
target: obj.styleTarget, | ||
styles: self.options.styles.dynamic, | ||
palette: obj.palette, | ||
obj: obj | ||
}); | ||
if (typeof self.options.afterEnterCallback === 'function') { self.options.afterEnterCallback.call(obj.styleTarget, obj, self.options, event); } | ||
} | ||
@@ -180,12 +419,17 @@ } | ||
/** | ||
* Leave Event listener callback | ||
* Removes the Box shadow from the target | ||
* @param event | ||
* Generates the leave event listener callback | ||
* Removes all dynamicStyles | ||
* @function | ||
* @param {paletteObj} obj | ||
* @name palettify#generateLeaveHandler | ||
* @return function | ||
*/ | ||
leaveHandler: function leaveHandler (obj) { | ||
generateLeaveHandler: function generateLeaveHandler (obj) { | ||
return function (event) { | ||
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget); | ||
var target = obj.styleTarget; | ||
if (target) { | ||
target.classList.remove(self.options.hoverClass); | ||
target.style.boxShadow = ''; | ||
if (typeof self.options.beforeLeaveCallback === 'function') { self.options.beforeLeaveCallback.call(obj.styleTarget, obj, self.options, event); } | ||
target.classList.remove(self.options.activeClass); | ||
__removeDynamicStylesFromElement(obj.styleTarget, self.options.styles.dynamic, self.options.styles.static, obj.palette); | ||
if (typeof self.options.afterLeaveCallback === 'function') { self.options.afterLeaveCallback.call(obj.styleTarget, obj, self.options, event); } | ||
} | ||
@@ -195,3 +439,5 @@ } | ||
/** | ||
* Attaches Event listeners to the hoverTargets | ||
* Attaches Event listeners to the eventTargets | ||
* @function | ||
* @name palettify#attachEventListeners | ||
*/ | ||
@@ -202,11 +448,23 @@ attachEventListeners: function attachEventListeners () { | ||
var leaveEvent = ref.leaveEvent; | ||
self.elements.forEach(function (obj) { | ||
obj.enterHandler = self.enterHandler(obj); | ||
obj.leaveHandler = self.leaveHandler(obj); | ||
obj.hoverTarget.addEventListener(enterEvent, obj.enterHandler, false); | ||
obj.hoverTarget.addEventListener(leaveEvent, obj.leaveHandler, false); | ||
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent; | ||
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent; | ||
self.data.forEach(function (obj) { | ||
obj.enterHandler = self.generateEnterHandler(obj); | ||
obj.leaveHandler = self.generateLeaveHandler(obj); | ||
enterEvent.forEach(function (event) { | ||
obj.eventTarget.addEventListener(event, obj.enterHandler, false); | ||
}); | ||
leaveEvent.forEach(function (event) { | ||
obj.eventTarget.addEventListener(event, obj.leaveHandler, false); | ||
}); | ||
}); | ||
}, | ||
/** | ||
* Detaches event listeners from hoverTargets | ||
* Detaches event listeners from eventTargets | ||
* @function | ||
* @name palettify#detachEventListeners | ||
*/ | ||
@@ -217,5 +475,13 @@ detachEventListeners: function detachEventListeners () { | ||
var leaveEvent = ref.leaveEvent; | ||
self.elements.forEach(function (obj) { | ||
obj.hoverTarget.removeEventListener(enterEvent, obj.enterHandler, false); | ||
obj.hoverTarget.removeEventListener(leaveEvent, obj.leaveHandler, false); | ||
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent; | ||
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent; | ||
self.data.forEach(function (obj) { | ||
enterEvent.forEach(function (event) { | ||
obj.eventTarget.removeEventListener(event, obj.enterHandler, false); | ||
}); | ||
leaveEvent.forEach(function (event) { | ||
obj.eventTarget.removeEventListener(event, obj.leaveHandler, false); | ||
}); | ||
}); | ||
@@ -226,2 +492,6 @@ }, | ||
* Gets fired when creating Palettify in the first place | ||
* @constructs | ||
* @param {DefaultOptions} opts - Options to pass to palettify | ||
* @name palettify#init | ||
* @return palettify | ||
*/ | ||
@@ -231,7 +501,7 @@ init: function init (opts) { | ||
self.options = Object.assign({}, __defaults, opts); | ||
__mergeOptions(opts); | ||
self.collectElements(); | ||
// Attach boxShadow's to all images | ||
self.attachBoxShadowToCollection(); | ||
// Collect colors and attach boxShadow's to all images | ||
self.extractColorsAndAttachStyles(); | ||
// Attach event listeners to all hovered elements. | ||
@@ -246,6 +516,12 @@ self.attachEventListeners(); | ||
* Destroys the Palettify and cleans up after it self. | ||
* @function | ||
* @name palettify#destroy | ||
* @param {Boolean} [cleanUp = true] | ||
*/ | ||
destroy: function destroy () { | ||
destroy: function destroy (cleanUp) { | ||
if ( cleanUp === void 0 ) cleanUp = true; | ||
self.detachEventListeners(); | ||
self.elements = []; | ||
cleanUp && self.cleanUp(); | ||
self.data = []; | ||
isInitialized = false; | ||
@@ -256,2 +532,4 @@ }, | ||
* Destroy and then reinitialize it. | ||
* @function | ||
* @name palettify#reInit | ||
*/ | ||
@@ -263,7 +541,21 @@ reInit: function reInit$1 () { | ||
/** | ||
* Cleans up the dom after the plugin is destroyed. Removes all styles.static | ||
* @function | ||
* @name palettify#cleanUp | ||
*/ | ||
cleanUp: function cleanUp$1 () { | ||
self.data.forEach(function (obj) { | ||
for (var prop in self.options.styles.static) { | ||
if (self.options.styles.static.hasOwnProperty(prop)) { | ||
obj.styleTarget.style[prop] = ''; | ||
} | ||
} | ||
}); | ||
}, | ||
/** | ||
* Set new options. Merges then with the old ones. | ||
* Takes an options object and a reInit boolean. | ||
* reInit defaults to True | ||
* @param {Object} options - New options to override the old ones | ||
* @param {Boolean} reInit - Should the plugin reInit? Defaults to true | ||
* @param {Boolean} [reInit = true] - Should the plugin reInit? Defaults to true | ||
* @function | ||
* @name palettify#setOptions | ||
*/ | ||
@@ -278,2 +570,3 @@ setOptions: function setOptions (options, reInit) { | ||
* Is the plugin initialized | ||
* @name palettify#isInitialized | ||
* @return {boolean} | ||
@@ -286,5 +579,167 @@ */ | ||
/** | ||
* Extract the colors from image tag or Background-image inline style | ||
* @param {HTMLElement} paletteTarget - The palette target to get the colors form | ||
* @param {Number} colorsToExtract - Number of colors to extract | ||
* @return {Promise} Returns promise when image is loaded with an array of RGB colors | ||
* @private | ||
*/ | ||
function __extractColors (paletteTarget, colorsToExtract) { | ||
var image = __sanitizeImage(paletteTarget); | ||
return new Promise(function (resolve) { | ||
if (!isImageLoaded(image)) { | ||
image.onload = function () { | ||
resolve(new colorThief_min().getPalette(image, colorsToExtract)); | ||
}; | ||
} else { | ||
resolve(new colorThief_min().getPalette(image, colorsToExtract)); | ||
} | ||
}) | ||
} | ||
/** | ||
* Transform all colors in the palette to RGBA colors using the supplied opacities in {@see palettify#options.opacities} | ||
* @param {Array} palette - Color palette of the current obj | ||
* @param {Array} opacities - Array of opacities for each color | ||
* @private | ||
*/ | ||
function __opacifyPalette (palette, opacities) { | ||
return palette.map(function (color, i) { | ||
var opacity = 1; | ||
if (Array.isArray(opacities) && opacities[i]) { | ||
opacity = opacities[i]; | ||
} | ||
return __getRgbaColor(color, opacity) | ||
}) | ||
} | ||
/** | ||
* Returns the rgba color with applied opacity | ||
* @param {Array} color | ||
* @param {String | Number} opacity | ||
* @return {string} | ||
* @private | ||
*/ | ||
function __getRgbaColor (color, opacity) { | ||
var | ||
r = color[0], | ||
g = color[1], | ||
b = color[2]; | ||
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")") | ||
} | ||
/** | ||
* Adds styles to an element. | ||
* @param target | ||
* @param {Object} styles | ||
* @param palette | ||
* @param staticCallback | ||
* @param obj | ||
* @private | ||
*/ | ||
function __attachStylesToElement (ref) { | ||
var target = ref.target; | ||
var styles = ref.styles; | ||
var palette = ref.palette; | ||
var staticCallback = ref.staticCallback; if ( staticCallback === void 0 ) staticCallback = null; | ||
var obj = ref.obj; | ||
for (var prop in styles) { | ||
if (styles.hasOwnProperty(prop)) { | ||
target.style[prop] = render_1(styles[prop], palette); | ||
} | ||
} | ||
staticCallback && staticCallback.call(target, obj, self.options); | ||
} | ||
/** | ||
* Removes all dynamic styles from an element. | ||
* If the staticStyle has the same prop as dynamicStyle, we set the prop to be the static style. | ||
* @param {HTMLElement} target - The Target to remove styles from | ||
* @param {Object} dynamicStyles - Dynamic styles to apply | ||
* @param {Object} staticStyles - Static styles to apply | ||
* @param {Array} palette - Array of available palettes | ||
* @private | ||
*/ | ||
function __removeDynamicStylesFromElement (target, dynamicStyles, staticStyles, palette) { | ||
for (var prop in dynamicStyles) { | ||
if (dynamicStyles.hasOwnProperty(prop) && !staticStyles.hasOwnProperty(prop)) { | ||
target.style[prop] = ''; | ||
} else if (staticStyles.hasOwnProperty(prop)) { | ||
target.style[prop] = render_1(staticStyles[prop], palette); | ||
} | ||
} | ||
} | ||
/** | ||
* Merges the options and throws errors where necessary | ||
* @param {DefaultOptions} options | ||
* @private | ||
*/ | ||
function __mergeOptions (options) { | ||
self.options = index$2$1(__defaults, options, {clone: true, arrayMerge: __arrayMerge}); | ||
Object.keys(self.options).forEach(function (opt) { | ||
if (self.options[opt] instanceof Error) { | ||
throw self.options[opt] | ||
} | ||
}); | ||
} | ||
function __getBrightness (rgb) { | ||
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 | ||
} | ||
function __isDark (color) { | ||
return __getBrightness(color) < 170 // 128 by default, but 170 gives better results | ||
} | ||
function __getInvertedColors (palette, colors) { | ||
return palette.map(function (color) { return __isDark(color) ? colors.light : colors.dark; }) | ||
} | ||
function __getUrl (url) { | ||
var el = document.createElement('a'); | ||
el.href = url; | ||
return el | ||
} | ||
function __isCORS (src) { | ||
return document.location.host !== __getUrl(src).host | ||
} | ||
function isImageLoaded (img) { | ||
if (!img.complete) { return false } | ||
if (typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0) { | ||
return false | ||
} | ||
// No other way of checking: assume it's ok. | ||
return true | ||
} | ||
function __sanitizeImage (imgElement) { | ||
var | ||
cachedImg = imgElement, | ||
isNotIMG = cachedImg.tagName !== 'IMG', | ||
src = isNotIMG ? imgElement.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') : imgElement.src, | ||
isCors = __isCORS(src); | ||
if (!cachedImg) { throw Error('Target is not an element', cachedImg) } | ||
// Our sample is not a img tag so we try to get its background image. | ||
if (isNotIMG || (isCors && !cachedImg.crossOrigin)) { | ||
if (isNotIMG && !cachedImg.style.backgroundImage) { throw Error('Tag provided is not an image and does not have a background-image style attached to it.') } | ||
cachedImg = new Image(imgElement.naturalWidth, imgElement.naturalHeight); | ||
isCors && (cachedImg.crossOrigin = 'anonymous'); | ||
cachedImg.src = src; | ||
} | ||
return cachedImg | ||
} | ||
function __arrayMerge (destArray, sourceArray, opts) { | ||
return sourceArray | ||
} | ||
return self | ||
} | ||
module.exports = palettify; | ||
module.exports = createPalettify; |
/*! | ||
* palettify v0.0.0 | ||
* palettify v1.0.0 | ||
* (c) 2017 Dobromir Hristov | ||
@@ -44,137 +44,376 @@ * Released under the MIT License. | ||
/** | ||
* palettify | ||
* @param {Object} opts | ||
* @param {String | NodeList} opts.imageTarget | ||
* @param {String | NodeList} opts.hoverTarget | ||
* @param {String} opts.attachBoxShadowTo | ||
* @param {String | Number} opts.opacity | ||
* @param {String} opts.opacitySecondary | ||
* @param {String} opts.colorIndexToUse | ||
* @param {String} opts.boxShadowTemplate - Provide a boxShadow template to apply. '0 2px 2px {color}, 3px 3px {colorSecondary}' | ||
* @callback ResolverFn | ||
* @param {string} varName - variable name before being parsed. | ||
* For example: {a.b.c} -> 'a.b.c', { x } -> 'x' | ||
* @param {Object} view - the view object that was passed to .render() function | ||
* @returns {string|number|boolean|Object|undefined} the value to be | ||
* interpolated. If the function returns undefined, the value resolution | ||
* algorithm will go ahead with the default behaviour (resolving the | ||
* variable name from the provided object). | ||
*/ | ||
function palettify () { | ||
/** | ||
* Extract the colors from image tag or Background-image inline style | ||
* @param {HTMLElement} paletteTarget - The palette target to get the colors form | ||
* @return {Array} Returns an object with RGB colors | ||
* @private | ||
*/ | ||
function __extractColors (paletteTarget) { | ||
var image = paletteTarget; | ||
if (!paletteTarget) { throw Error('Target is not an element', paletteTarget) } | ||
// Our sample is not a img tag so we try to get its background image. | ||
if (paletteTarget.tagName !== 'IMG' && paletteTarget.style.backgroundImage) { Error('Tag provided is not an image and does not have a background-image style attached to it.'); } | ||
// Its not an IMG tag so we try to crate one under the hood. | ||
if (paletteTarget.tagName !== 'IMG') { | ||
image = new Image(paletteTarget.offsetWidth, paletteTarget.offsetHeight); | ||
image.src = paletteTarget.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, ''); | ||
var VAR_MATCH_REGEX = /\{\{\s*(.*?)\s*\}\}/g; | ||
function _valueToString (value) { | ||
switch (typeof value) { | ||
case 'string': | ||
case 'number': | ||
case 'boolean': | ||
return value; | ||
case 'object': | ||
try { | ||
// null is an object but is falsy. Swallow it. | ||
return value === null ? '' : JSON.stringify(value); | ||
} catch (jsonError) { | ||
return '{...}'; | ||
} | ||
return new colorThief_min().getPalette(image, 3) | ||
default: | ||
// Anything else will be replaced with an empty string | ||
// For example: undefined, Symbol, etc. | ||
return ''; | ||
} | ||
} | ||
/** | ||
* Returns the rgba color from current color with applied opacity | ||
* @param {Array} color | ||
* @param {String | Number} opacity | ||
* @return {string} | ||
* @private | ||
*/ | ||
function __getRgbaColor (color, opacity) { | ||
var | ||
r = color[0], | ||
g = color[1], | ||
b = color[2]; | ||
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")") | ||
/** | ||
* Recursively goes through an object trying to resolve a path. | ||
* | ||
* @param {Object} scope - The object to traverse (in each recursive call we dig into this object) | ||
* @param {string[]} path - An array of property names to traverse one-by-one | ||
* @param {number} [pathIndex=0] - The current index in the path array | ||
*/ | ||
function _recursivePathResolver(scope, path, pathIndex) { | ||
if ( pathIndex === void 0 ) pathIndex = 0; | ||
if (typeof scope !== 'object' || scope === null || scope === undefined) { | ||
return ''; | ||
} | ||
/** | ||
* Adds boxShadow to an element. | ||
* Required the color and the element to attach to | ||
* @param {Array} colors - Array of RGB Colors | ||
* @param {Object} obj - Object that holds the hoverTarget and image | ||
* @param {HTMLElement} obj.hoverTarget - Reference to the hoverTarget dom element | ||
* @param {HTMLElement} obj.image - Reference to the image dom element | ||
* @param {Array} obj.colors - Array of RGB Colors | ||
* @param {String} obj.rgbaColor - rgba transformed color with opacity in mind. | ||
* @param {String} obj.rgbaColorSecondary - rgba transformed color with secondary opacity in mind. | ||
* @private | ||
*/ | ||
function __addBoxShadowToElementData (colors, obj) { | ||
var ref = self.options; | ||
var opacity = ref.opacity; | ||
var opacitySecondary = ref.opacitySecondary; | ||
var boxShadowTemplate = ref.boxShadowTemplate; | ||
var color = colors[self.options.colorIndexToUse], | ||
rgbaColor = __getRgbaColor(color, opacity), | ||
rgbaColorSecondary = __getRgbaColor(color, opacitySecondary); | ||
var boxShadow; | ||
if (!boxShadowTemplate) { | ||
boxShadow = "0 2px 2px " + rgbaColor + ", 0 4px 4px " + rgbaColor + ", 0 8px 8px " + rgbaColor + ", 0 16px 16px " + rgbaColor + ", 0 32px 32px " + rgbaColorSecondary + ", 0 64px 64px " + rgbaColorSecondary; | ||
var varName = path[pathIndex]; | ||
var value = scope[varName]; | ||
if (pathIndex === path.length - 1) { | ||
// It's a leaf, return whatever it is | ||
return value; | ||
} | ||
return _recursivePathResolver(value, path, ++pathIndex); | ||
} | ||
function defaultResolver(varName, view) { | ||
return _recursivePathResolver(view, varName.split('.')); | ||
} | ||
/** | ||
* Replaces every {{variable}} inside the template with values provided by view. | ||
* | ||
* @param {string} template - The template containing one or more {{variableNames}} every variable | ||
* names that is used in the template. If it's omitted, it'll be assumed an empty object. | ||
* @param {Object} [view={}] - An object containing values for every variable names that is used in | ||
* the template. If it's omitted, it'll be set to an empty object essentially removing all | ||
* {{varName}}s in the template. | ||
* @param {ResolverFn} [resolver] - An optional function that will be | ||
* called for every {{varName}} to generate a value. If the resolver throws an error | ||
* we'll proceed with the default value resolution algorithm (find the value from the view | ||
* object). | ||
* @returns {string} - Template where its variable names replaced with | ||
* corresponding values. If a value is not found or is invalid, it will | ||
* be assumed empty string ''. If the value is an object itself, it'll | ||
* be stringified by JSON. | ||
* In case of a JSON stringify error the result will look like "{...}". | ||
*/ | ||
function render (template, view, resolver) { | ||
if ( view === void 0 ) view = {}; | ||
if ( resolver === void 0 ) resolver = defaultResolver; | ||
// don't touch the template if it is not a string | ||
if (typeof template !== 'string') { | ||
return template; | ||
} | ||
return template.replace(VAR_MATCH_REGEX, function (match, varName) { | ||
try { | ||
// defaultResolver never throws | ||
return _valueToString(resolver(varName, view)); | ||
} catch (e) { | ||
// if your resolver throws, we proceed with the default resolver | ||
return _valueToString(defaultResolver(varName, view)); | ||
} | ||
}); | ||
} | ||
var render_1$1 = render; | ||
var render_1 = render_1$1; | ||
var index$2 = function isMergeableObject(value) { | ||
return isNonNullObject(value) && isNotSpecial(value) | ||
}; | ||
function isNonNullObject(value) { | ||
return !!value && typeof value === 'object' | ||
} | ||
function isNotSpecial(value) { | ||
var stringValue = Object.prototype.toString.call(value); | ||
return stringValue !== '[object RegExp]' | ||
&& stringValue !== '[object Date]' | ||
} | ||
function emptyTarget(val) { | ||
return Array.isArray(val) ? [] : {} | ||
} | ||
function cloneIfNecessary(value, optionsArgument) { | ||
var clone = optionsArgument && optionsArgument.clone === true; | ||
return (clone && index$2(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value | ||
} | ||
function defaultArrayMerge(target, source, optionsArgument) { | ||
var destination = target.slice(); | ||
source.forEach(function(e, i) { | ||
if (typeof destination[i] === 'undefined') { | ||
destination[i] = cloneIfNecessary(e, optionsArgument); | ||
} else if (index$2(e)) { | ||
destination[i] = deepmerge(target[i], e, optionsArgument); | ||
} else if (target.indexOf(e) === -1) { | ||
destination.push(cloneIfNecessary(e, optionsArgument)); | ||
} | ||
}); | ||
return destination | ||
} | ||
function mergeObject(target, source, optionsArgument) { | ||
var destination = {}; | ||
if (index$2(target)) { | ||
Object.keys(target).forEach(function(key) { | ||
destination[key] = cloneIfNecessary(target[key], optionsArgument); | ||
}); | ||
} | ||
Object.keys(source).forEach(function(key) { | ||
if (!index$2(source[key]) || !target[key]) { | ||
destination[key] = cloneIfNecessary(source[key], optionsArgument); | ||
} else { | ||
destination[key] = deepmerge(target[key], source[key], optionsArgument); | ||
} | ||
}); | ||
return destination | ||
} | ||
function deepmerge(target, source, optionsArgument) { | ||
var array = Array.isArray(source); | ||
var options = optionsArgument || { arrayMerge: defaultArrayMerge }; | ||
var arrayMerge = options.arrayMerge || defaultArrayMerge; | ||
if (array) { | ||
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument) | ||
} else { | ||
boxShadow = boxShadowTemplate.replace('{color}', rgbaColor).replace('{colorSecondary}', rgbaColorSecondary); | ||
return mergeObject(target, source, optionsArgument) | ||
} | ||
obj.colors = colors; | ||
obj.rgbaColor = rgbaColor; | ||
obj.rgbaColorSecondary = rgbaColorSecondary; | ||
obj.boxShadow = boxShadow; | ||
} | ||
} | ||
var isInitialized = false; | ||
deepmerge.all = function deepmergeAll(array, optionsArgument) { | ||
if (!Array.isArray(array) || array.length < 2) { | ||
throw new Error('first argument should be an array with at least two elements') | ||
} | ||
// we are sure there are at least 2 values, so it is safe to have no initial value | ||
return array.reduce(function(prev, next) { | ||
return deepmerge(prev, next, optionsArgument) | ||
}) | ||
}; | ||
var index$2$1 = deepmerge; | ||
/** | ||
* Creates a palettify instance | ||
* @module palettify | ||
* @return palettify | ||
*/ | ||
function createPalettify () { | ||
var | ||
isInitialized = false, | ||
__selector = null; | ||
/** | ||
* @typedef {Object} paletteObj | ||
* @property {HTMLElement} eventTarget | ||
* @property {HTMLElement} styleTarget | ||
* @property {HTMLElement} image | ||
* @property {PaletteObject} palette | ||
* @property {Function} enterHandler | ||
* @property {Function} leaveHandler | ||
*/ | ||
/** | ||
* @typedef {Object} PaletteObject | ||
* @property {Array} original | ||
* @property {Array} rgb | ||
* @property {Array} rgba | ||
*/ | ||
var | ||
/** | ||
* Default options to extend | ||
* @typedef {Object} DefaultOptions | ||
* @property {String|HTMLElement} selector - The element that holds all your images and eventTargets. | ||
* @property {String} eventTarget - The event target to attach event listeners to | ||
* @property {String} image - The image to sample | ||
* @property {String} styleTarget - The element to apply styling to. Defaults to image | ||
* @property {Object} contrastColors - Light and Dark colors, based on brightness of each color in the palette | ||
* @property {String} contrastColors.light - Light inverted color | ||
* @property {String} contrastColors.dark - Dark inverted color | ||
* @property {String} activeClass - CSS class to apply on each enterEvent | ||
* @property {String} readyClass - CSS class to apply when palettify is ready | ||
* @property {Number} colorsToExtract - Colors to extract | ||
* @property {String | Array} enterEvent - Event or Array of events to apply listeners to for each enterCallback | ||
* @property {string | Array} leaveEvent - Event or Array of events to apply listeners to for each leaveCallback | ||
* @property {Object} styles - Collection of static and dynamic styles | ||
* @property {Array<number>} styles.opacities - Array of opacities | ||
* @property {Object} styles.static - Object containing valid css styles to apply to styleTarget on ready | ||
* @property {Object} styles.dynamic - Object containing valid css styles to apply to styleTarget on each enterEvent | ||
* @property {Function} beforeEnterCallback - Callback called before the enter event | ||
* @property {Function} afterEnterCallback - Callback called after the enter event | ||
* @property {Function} beforeLeaveCallback - Callback called before the leave event | ||
* @property {Function} afterLeaveCallback - Callback called after the enter event | ||
* @property {Function} onReadyCallback - Callback called after palettify is ready. | ||
*/ | ||
__defaults = { | ||
hoverTarget: Error('Please provide hoverTarget'), | ||
imageTarget: Error('Please provide ImageSrc'), | ||
attachBoxShadowTo: null, | ||
opacity: 0.2, | ||
opacitySecondary: 0.2, | ||
hoverClass: 'box-shadow-attached', | ||
colorIndexToUse: 0, | ||
selector: Error('Please provide а selector in your options.'), | ||
eventTarget: Error('Please provide an eventTarget as a parent for your image in the options.'), | ||
image: Error('Please provide an image to sample.'), | ||
styleTarget: null, | ||
contrastColors: { | ||
light: '#fff', | ||
dark: '#000' | ||
}, | ||
activeClass: 'palettify--active', | ||
readyClass: 'palettify--ready', | ||
colorsToExtract: 3, | ||
enterEvent: 'mouseenter', | ||
leaveEvent: 'mouseleave', | ||
boxShadowTemplate: '' | ||
styles: { | ||
opacities: [0.5, 0.5, 0.5], | ||
static: {}, | ||
dynamic: {} | ||
}, | ||
staticCallback: null, | ||
beforeEnterCallback: null, | ||
afterEnterCallback: null, | ||
beforeLeaveCallback: null, | ||
afterLeaveCallback: null, | ||
onReadyCallback: null | ||
}, | ||
/** | ||
* @typedef {Object} palettify | ||
* @type Object | ||
* @property {DefaultOptions} options | ||
* @property {Array} data | ||
* @property {function} collectElements | ||
* @property {function} extractColorsAndAttachStyles | ||
* @property {function} generateEnterHandler | ||
* @property {function} generateLeaveHandler | ||
* @property {function} attachEventListeners | ||
* @property {function} detachEventListeners | ||
* @property {function} init | ||
* @property {function} destroy | ||
* @property {function} reInit | ||
* @property {function} cleanUp | ||
* @property {function} setOptions | ||
* @property {function} isInitialized | ||
*/ | ||
self = { | ||
/** | ||
* Palettify options | ||
* @name palettify#options | ||
* @type DefaultOptions | ||
*/ | ||
options: {}, | ||
elements: [], | ||
/** | ||
* Gather all needed elements and push into an the `elements` array | ||
* Holds a collection of {@link paletteObj} objects. | ||
* @type Array<paletteObj> | ||
* @name palettify#data | ||
*/ | ||
data: [], | ||
/** | ||
* Gather all needed elements and push into an the {@see palettify#data} | ||
* @type function | ||
* @name palettify#collectElements | ||
*/ | ||
collectElements: function collectElements () { | ||
var hoverTargetsCollection = self.options.hoverTarget; | ||
if (typeof hoverTargetsCollection === 'string') { | ||
hoverTargetsCollection = document.querySelectorAll(hoverTargetsCollection); | ||
} | ||
if (!hoverTargetsCollection.length) { return } | ||
for (var i = 0; i < hoverTargetsCollection.length; i++) { | ||
var | ||
eventTargetsCollection = ''; | ||
__selector = typeof self.options.selector === 'string' ? document.querySelector(self.options.selector) : self.options.selector; | ||
if (!__selector) { throw new Error('Selector ' + self.options.selector + ' does not exist') } | ||
eventTargetsCollection = __selector.querySelectorAll(self.options.eventTarget); | ||
[].slice.call(eventTargetsCollection, 0).forEach(function (eventTarget) { | ||
var | ||
element = hoverTargetsCollection[i], | ||
image = element.querySelector(self.options.imageTarget), | ||
image = eventTarget.querySelector(self.options.image), | ||
styleTarget = self.options.styleTarget === true ? eventTarget : eventTarget.querySelector(self.options.styleTarget || self.options.image), | ||
// Create the main object it self. | ||
obj = { | ||
hoverTarget: element, | ||
image: image | ||
eventTarget: eventTarget, | ||
styleTarget: styleTarget, | ||
image: image, | ||
palette: { | ||
original: [], | ||
rgb: [], | ||
rgba: [] | ||
} | ||
}; | ||
self.elements.push(obj); | ||
} | ||
self.data.push(obj); | ||
}); | ||
}, | ||
/** | ||
* Attaches boxShadow to the collection of Images | ||
* Extracts colors and attaches static styles to each styleTarget {@see palettify#options.styleTarget} | ||
* Adds ready class and calls onReadyCallback when done | ||
* @function | ||
* @name palettify#extractColorsAndAttachStyles | ||
*/ | ||
attachBoxShadowToCollection: function attachBoxShadowToCollection () { | ||
self.elements.forEach(function (obj) { | ||
var color = __extractColors(obj.image); | ||
__addBoxShadowToElementData(color, obj); | ||
extractColorsAndAttachStyles: function extractColorsAndAttachStyles (skipCallbacks) { | ||
if ( skipCallbacks === void 0 ) skipCallbacks = false; | ||
var promises = []; | ||
self.data.forEach(function (obj, index) { | ||
promises[index] = __extractColors(obj.image, self.options.colorsToExtract).then(function (colors) { | ||
obj.palette.original = colors; | ||
obj.palette.rgb = __opacifyPalette(obj.palette.original, []); | ||
obj.palette.rgba = __opacifyPalette(obj.palette.original, self.options.styles.opacities); | ||
obj.palette.contrastColors = __getInvertedColors(obj.palette.original, self.options.contrastColors); | ||
__attachStylesToElement({ | ||
target: obj.styleTarget, | ||
styles: self.options.styles.static, | ||
palette: obj.palette, | ||
staticCallback: self.options.staticCallback, | ||
obj: obj | ||
}); | ||
}); | ||
}); | ||
if (!skipCallbacks) { | ||
Promise.all(promises).then(function (values) { | ||
__selector.classList.add(self.options.readyClass); | ||
typeof self.options.onReadyCallback === 'function' && self.options.onReadyCallback.call(self, self); | ||
}); | ||
} | ||
}, | ||
/** | ||
* Enter event listener callback | ||
* Adds the boxShadow to the target | ||
* @param event | ||
* Generates the enter event listener callback | ||
* Attaches dynamicStyles {@see palettify#options.styles.dynamic} to styleTarget | ||
* @function | ||
* @param {paletteObj} obj | ||
* @name palettify#generateEnterHandler | ||
* @return function | ||
*/ | ||
enterHandler: function enterHandler (obj) { | ||
generateEnterHandler: function generateEnterHandler (obj) { | ||
return function (event) { | ||
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget); | ||
if (target) { | ||
target.classList.add(self.options.hoverClass); | ||
target.style.boxShadow = obj.boxShadow; | ||
if (obj.styleTarget) { | ||
if (typeof self.options.beforeEnterCallback === 'function') { self.options.beforeEnterCallback.call(obj.styleTarget, obj, self.options, event); } | ||
obj.styleTarget.classList.add(self.options.activeClass); | ||
__attachStylesToElement({ | ||
target: obj.styleTarget, | ||
styles: self.options.styles.dynamic, | ||
palette: obj.palette, | ||
obj: obj | ||
}); | ||
if (typeof self.options.afterEnterCallback === 'function') { self.options.afterEnterCallback.call(obj.styleTarget, obj, self.options, event); } | ||
} | ||
@@ -184,12 +423,17 @@ } | ||
/** | ||
* Leave Event listener callback | ||
* Removes the Box shadow from the target | ||
* @param event | ||
* Generates the leave event listener callback | ||
* Removes all dynamicStyles | ||
* @function | ||
* @param {paletteObj} obj | ||
* @name palettify#generateLeaveHandler | ||
* @return function | ||
*/ | ||
leaveHandler: function leaveHandler (obj) { | ||
generateLeaveHandler: function generateLeaveHandler (obj) { | ||
return function (event) { | ||
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget); | ||
var target = obj.styleTarget; | ||
if (target) { | ||
target.classList.remove(self.options.hoverClass); | ||
target.style.boxShadow = ''; | ||
if (typeof self.options.beforeLeaveCallback === 'function') { self.options.beforeLeaveCallback.call(obj.styleTarget, obj, self.options, event); } | ||
target.classList.remove(self.options.activeClass); | ||
__removeDynamicStylesFromElement(obj.styleTarget, self.options.styles.dynamic, self.options.styles.static, obj.palette); | ||
if (typeof self.options.afterLeaveCallback === 'function') { self.options.afterLeaveCallback.call(obj.styleTarget, obj, self.options, event); } | ||
} | ||
@@ -199,3 +443,5 @@ } | ||
/** | ||
* Attaches Event listeners to the hoverTargets | ||
* Attaches Event listeners to the eventTargets | ||
* @function | ||
* @name palettify#attachEventListeners | ||
*/ | ||
@@ -206,11 +452,23 @@ attachEventListeners: function attachEventListeners () { | ||
var leaveEvent = ref.leaveEvent; | ||
self.elements.forEach(function (obj) { | ||
obj.enterHandler = self.enterHandler(obj); | ||
obj.leaveHandler = self.leaveHandler(obj); | ||
obj.hoverTarget.addEventListener(enterEvent, obj.enterHandler, false); | ||
obj.hoverTarget.addEventListener(leaveEvent, obj.leaveHandler, false); | ||
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent; | ||
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent; | ||
self.data.forEach(function (obj) { | ||
obj.enterHandler = self.generateEnterHandler(obj); | ||
obj.leaveHandler = self.generateLeaveHandler(obj); | ||
enterEvent.forEach(function (event) { | ||
obj.eventTarget.addEventListener(event, obj.enterHandler, false); | ||
}); | ||
leaveEvent.forEach(function (event) { | ||
obj.eventTarget.addEventListener(event, obj.leaveHandler, false); | ||
}); | ||
}); | ||
}, | ||
/** | ||
* Detaches event listeners from hoverTargets | ||
* Detaches event listeners from eventTargets | ||
* @function | ||
* @name palettify#detachEventListeners | ||
*/ | ||
@@ -221,5 +479,13 @@ detachEventListeners: function detachEventListeners () { | ||
var leaveEvent = ref.leaveEvent; | ||
self.elements.forEach(function (obj) { | ||
obj.hoverTarget.removeEventListener(enterEvent, obj.enterHandler, false); | ||
obj.hoverTarget.removeEventListener(leaveEvent, obj.leaveHandler, false); | ||
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent; | ||
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent; | ||
self.data.forEach(function (obj) { | ||
enterEvent.forEach(function (event) { | ||
obj.eventTarget.removeEventListener(event, obj.enterHandler, false); | ||
}); | ||
leaveEvent.forEach(function (event) { | ||
obj.eventTarget.removeEventListener(event, obj.leaveHandler, false); | ||
}); | ||
}); | ||
@@ -230,2 +496,6 @@ }, | ||
* Gets fired when creating Palettify in the first place | ||
* @constructs | ||
* @param {DefaultOptions} opts - Options to pass to palettify | ||
* @name palettify#init | ||
* @return palettify | ||
*/ | ||
@@ -235,7 +505,7 @@ init: function init (opts) { | ||
self.options = Object.assign({}, __defaults, opts); | ||
__mergeOptions(opts); | ||
self.collectElements(); | ||
// Attach boxShadow's to all images | ||
self.attachBoxShadowToCollection(); | ||
// Collect colors and attach boxShadow's to all images | ||
self.extractColorsAndAttachStyles(); | ||
// Attach event listeners to all hovered elements. | ||
@@ -250,6 +520,12 @@ self.attachEventListeners(); | ||
* Destroys the Palettify and cleans up after it self. | ||
* @function | ||
* @name palettify#destroy | ||
* @param {Boolean} [cleanUp = true] | ||
*/ | ||
destroy: function destroy () { | ||
destroy: function destroy (cleanUp) { | ||
if ( cleanUp === void 0 ) cleanUp = true; | ||
self.detachEventListeners(); | ||
self.elements = []; | ||
cleanUp && self.cleanUp(); | ||
self.data = []; | ||
isInitialized = false; | ||
@@ -260,2 +536,4 @@ }, | ||
* Destroy and then reinitialize it. | ||
* @function | ||
* @name palettify#reInit | ||
*/ | ||
@@ -267,7 +545,21 @@ reInit: function reInit$1 () { | ||
/** | ||
* Cleans up the dom after the plugin is destroyed. Removes all styles.static | ||
* @function | ||
* @name palettify#cleanUp | ||
*/ | ||
cleanUp: function cleanUp$1 () { | ||
self.data.forEach(function (obj) { | ||
for (var prop in self.options.styles.static) { | ||
if (self.options.styles.static.hasOwnProperty(prop)) { | ||
obj.styleTarget.style[prop] = ''; | ||
} | ||
} | ||
}); | ||
}, | ||
/** | ||
* Set new options. Merges then with the old ones. | ||
* Takes an options object and a reInit boolean. | ||
* reInit defaults to True | ||
* @param {Object} options - New options to override the old ones | ||
* @param {Boolean} reInit - Should the plugin reInit? Defaults to true | ||
* @param {Boolean} [reInit = true] - Should the plugin reInit? Defaults to true | ||
* @function | ||
* @name palettify#setOptions | ||
*/ | ||
@@ -282,2 +574,3 @@ setOptions: function setOptions (options, reInit) { | ||
* Is the plugin initialized | ||
* @name palettify#isInitialized | ||
* @return {boolean} | ||
@@ -290,7 +583,169 @@ */ | ||
/** | ||
* Extract the colors from image tag or Background-image inline style | ||
* @param {HTMLElement} paletteTarget - The palette target to get the colors form | ||
* @param {Number} colorsToExtract - Number of colors to extract | ||
* @return {Promise} Returns promise when image is loaded with an array of RGB colors | ||
* @private | ||
*/ | ||
function __extractColors (paletteTarget, colorsToExtract) { | ||
var image = __sanitizeImage(paletteTarget); | ||
return new Promise(function (resolve) { | ||
if (!isImageLoaded(image)) { | ||
image.onload = function () { | ||
resolve(new colorThief_min().getPalette(image, colorsToExtract)); | ||
}; | ||
} else { | ||
resolve(new colorThief_min().getPalette(image, colorsToExtract)); | ||
} | ||
}) | ||
} | ||
/** | ||
* Transform all colors in the palette to RGBA colors using the supplied opacities in {@see palettify#options.opacities} | ||
* @param {Array} palette - Color palette of the current obj | ||
* @param {Array} opacities - Array of opacities for each color | ||
* @private | ||
*/ | ||
function __opacifyPalette (palette, opacities) { | ||
return palette.map(function (color, i) { | ||
var opacity = 1; | ||
if (Array.isArray(opacities) && opacities[i]) { | ||
opacity = opacities[i]; | ||
} | ||
return __getRgbaColor(color, opacity) | ||
}) | ||
} | ||
/** | ||
* Returns the rgba color with applied opacity | ||
* @param {Array} color | ||
* @param {String | Number} opacity | ||
* @return {string} | ||
* @private | ||
*/ | ||
function __getRgbaColor (color, opacity) { | ||
var | ||
r = color[0], | ||
g = color[1], | ||
b = color[2]; | ||
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")") | ||
} | ||
/** | ||
* Adds styles to an element. | ||
* @param target | ||
* @param {Object} styles | ||
* @param palette | ||
* @param staticCallback | ||
* @param obj | ||
* @private | ||
*/ | ||
function __attachStylesToElement (ref) { | ||
var target = ref.target; | ||
var styles = ref.styles; | ||
var palette = ref.palette; | ||
var staticCallback = ref.staticCallback; if ( staticCallback === void 0 ) staticCallback = null; | ||
var obj = ref.obj; | ||
for (var prop in styles) { | ||
if (styles.hasOwnProperty(prop)) { | ||
target.style[prop] = render_1(styles[prop], palette); | ||
} | ||
} | ||
staticCallback && staticCallback.call(target, obj, self.options); | ||
} | ||
/** | ||
* Removes all dynamic styles from an element. | ||
* If the staticStyle has the same prop as dynamicStyle, we set the prop to be the static style. | ||
* @param {HTMLElement} target - The Target to remove styles from | ||
* @param {Object} dynamicStyles - Dynamic styles to apply | ||
* @param {Object} staticStyles - Static styles to apply | ||
* @param {Array} palette - Array of available palettes | ||
* @private | ||
*/ | ||
function __removeDynamicStylesFromElement (target, dynamicStyles, staticStyles, palette) { | ||
for (var prop in dynamicStyles) { | ||
if (dynamicStyles.hasOwnProperty(prop) && !staticStyles.hasOwnProperty(prop)) { | ||
target.style[prop] = ''; | ||
} else if (staticStyles.hasOwnProperty(prop)) { | ||
target.style[prop] = render_1(staticStyles[prop], palette); | ||
} | ||
} | ||
} | ||
/** | ||
* Merges the options and throws errors where necessary | ||
* @param {DefaultOptions} options | ||
* @private | ||
*/ | ||
function __mergeOptions (options) { | ||
self.options = index$2$1(__defaults, options, {clone: true, arrayMerge: __arrayMerge}); | ||
Object.keys(self.options).forEach(function (opt) { | ||
if (self.options[opt] instanceof Error) { | ||
throw self.options[opt] | ||
} | ||
}); | ||
} | ||
function __getBrightness (rgb) { | ||
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 | ||
} | ||
function __isDark (color) { | ||
return __getBrightness(color) < 170 // 128 by default, but 170 gives better results | ||
} | ||
function __getInvertedColors (palette, colors) { | ||
return palette.map(function (color) { return __isDark(color) ? colors.light : colors.dark; }) | ||
} | ||
function __getUrl (url) { | ||
var el = document.createElement('a'); | ||
el.href = url; | ||
return el | ||
} | ||
function __isCORS (src) { | ||
return document.location.host !== __getUrl(src).host | ||
} | ||
function isImageLoaded (img) { | ||
if (!img.complete) { return false } | ||
if (typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0) { | ||
return false | ||
} | ||
// No other way of checking: assume it's ok. | ||
return true | ||
} | ||
function __sanitizeImage (imgElement) { | ||
var | ||
cachedImg = imgElement, | ||
isNotIMG = cachedImg.tagName !== 'IMG', | ||
src = isNotIMG ? imgElement.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') : imgElement.src, | ||
isCors = __isCORS(src); | ||
if (!cachedImg) { throw Error('Target is not an element', cachedImg) } | ||
// Our sample is not a img tag so we try to get its background image. | ||
if (isNotIMG || (isCors && !cachedImg.crossOrigin)) { | ||
if (isNotIMG && !cachedImg.style.backgroundImage) { throw Error('Tag provided is not an image and does not have a background-image style attached to it.') } | ||
cachedImg = new Image(imgElement.naturalWidth, imgElement.naturalHeight); | ||
isCors && (cachedImg.crossOrigin = 'anonymous'); | ||
cachedImg.src = src; | ||
} | ||
return cachedImg | ||
} | ||
function __arrayMerge (destArray, sourceArray, opts) { | ||
return sourceArray | ||
} | ||
return self | ||
} | ||
return palettify; | ||
return createPalettify; | ||
}))); |
/*! | ||
* palettify v0.0.0 | ||
* palettify v1.0.0 | ||
* (c) 2017 Dobromir Hristov | ||
* Released under the MIT License. | ||
*/ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.palettify=t()}(this,function(){"use strict";function e(){function e(e){var r=e;if(!e)throw Error("Target is not an element",e);return"IMG"!==e.tagName&&e.style.backgroundImage&&Error("Tag provided is not an image and does not have a background-image style attached to it."),"IMG"!==e.tagName&&(r=new Image(e.offsetWidth,e.offsetHeight),r.src=e.style.backgroundImage.replace("url(","").replace(")","").replace(/"/gi,"")),(new t).getPalette(r,3)}function r(e,t){return"rgba("+e[0]+", "+e[1]+", "+e[2]+", "+t+")"}function n(e,t){var n,o=i.options,a=o.opacity,u=o.opacitySecondary,c=o.boxShadowTemplate,s=e[i.options.colorIndexToUse],f=r(s,a),h=r(s,u);n=c?c.replace("{color}",f).replace("{colorSecondary}",h):"0 2px 2px "+f+", 0 4px 4px "+f+", 0 8px 8px "+f+", 0 16px 16px "+f+", 0 32px 32px "+h+", 0 64px 64px "+h,t.colors=e,t.rgbaColor=f,t.rgbaColorSecondary=h,t.boxShadow=n}var o=!1,a={hoverTarget:Error("Please provide hoverTarget"),imageTarget:Error("Please provide ImageSrc"),attachBoxShadowTo:null,opacity:.2,opacitySecondary:.2,hoverClass:"box-shadow-attached",colorIndexToUse:0,enterEvent:"mouseenter",leaveEvent:"mouseleave",boxShadowTemplate:""},i={options:{},elements:[],collectElements:function(){var e=i.options.hoverTarget;if("string"==typeof e&&(e=document.querySelectorAll(e)),e.length)for(var t=0;t<e.length;t++){var r=e[t],n=r.querySelector(i.options.imageTarget),o={hoverTarget:r,image:n};i.elements.push(o)}},attachBoxShadowToCollection:function(){i.elements.forEach(function(t){n(e(t.image),t)})},enterHandler:function(e){return function(t){var r=t.currentTarget.querySelector(i.options.attachBoxShadowTo||i.options.imageTarget);r&&(r.classList.add(i.options.hoverClass),r.style.boxShadow=e.boxShadow)}},leaveHandler:function(e){return function(e){var t=e.currentTarget.querySelector(i.options.attachBoxShadowTo||i.options.imageTarget);t&&(t.classList.remove(i.options.hoverClass),t.style.boxShadow="")}},attachEventListeners:function(){var e=i.options,t=e.enterEvent,r=e.leaveEvent;i.elements.forEach(function(e){e.enterHandler=i.enterHandler(e),e.leaveHandler=i.leaveHandler(e),e.hoverTarget.addEventListener(t,e.enterHandler,!1),e.hoverTarget.addEventListener(r,e.leaveHandler,!1)})},detachEventListeners:function(){var e=i.options,t=e.enterEvent,r=e.leaveEvent;i.elements.forEach(function(e){e.hoverTarget.removeEventListener(t,e.enterHandler,!1),e.hoverTarget.removeEventListener(r,e.leaveHandler,!1)})},init:function(e){if(o)throw new Error("Palettify is already initialized");return i.options=Object.assign({},a,e),i.collectElements(),i.attachBoxShadowToCollection(),i.attachEventListeners(),o=!0,i},destroy:function(){i.detachEventListeners(),i.elements=[],o=!1},reInit:function(){i.destroy(),i.init(i.options)},setOptions:function(e,t){void 0===t&&(t=!0),i.options=Object.assign({},i.options,e),t&&i.reInit()},isInitialized:function(){return o}};return i}var t=function(){var e=function(){},t=function(e){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),document.body.appendChild(this.canvas),this.width=this.canvas.width=e.width,this.height=this.canvas.height=e.height,this.context.drawImage(e,0,0,this.width,this.height)};t.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},t.prototype.update=function(e){this.context.putImageData(e,0,0)},t.prototype.getPixelCount=function(){return this.width*this.height},t.prototype.getImageData=function(){return this.context.getImageData(0,0,this.width,this.height)},t.prototype.removeCanvas=function(){this.canvas.parentNode.removeChild(this.canvas)};var r={map:function(e,t){var r={};return t?e.map(function(e,n){return r.index=n,t.call(r,e)}):e.slice()},naturalOrder:function(e,t){return e<t?-1:e>t?1:0},sum:function(e,t){var r={};return e.reduce(t?function(e,n,o){return r.index=o,e+t.call(r,n)}:function(e,t){return e+t},0)},max:function(e,t){return Math.max.apply(null,t?r.map(e,t):e)}},n=function(){function e(e,t,r){return(e<<2*s)+(t<<s)+r}function t(e){function t(){r.sort(e),n=!0}var r=[],n=!1;return{push:function(e){r.push(e),n=!1},peek:function(e){return n||t(),void 0===e&&(e=r.length-1),r[e]},pop:function(){return n||t(),r.pop()},size:function(){return r.length},map:function(e){return r.map(e)},debug:function(){return n||t(),r}}}function n(e,t,r,n,o,a,i){var u=this;u.r1=e,u.r2=t,u.g1=r,u.g2=n,u.b1=o,u.b2=a,u.histo=i}function o(){this.vboxes=new t(function(e,t){return r.naturalOrder(e.vbox.count()*e.vbox.volume(),t.vbox.count()*t.vbox.volume())})}function a(t){var r,n,o,a,i=1<<3*s,u=new Array(i);return t.forEach(function(t){n=t[0]>>f,o=t[1]>>f,a=t[2]>>f,r=e(n,o,a),u[r]=(u[r]||0)+1}),u}function i(e,t){var r,o,a,i=1e6,u=0,c=1e6,s=0,h=1e6,l=0;return e.forEach(function(e){r=e[0]>>f,o=e[1]>>f,a=e[2]>>f,r<i?i=r:r>u&&(u=r),o<c?c=o:o>s&&(s=o),a<h?h=a:a>l&&(l=a)}),new n(i,u,c,s,h,l,t)}function u(t,n){if(n.count()){var o=n.r2-n.r1+1,a=n.g2-n.g1+1,i=n.b2-n.b1+1,u=r.max([o,a,i]);if(1==n.count())return[n.copy()];var c,s,f,h,l,p=0,v=[],g=[];if(u==o)for(c=n.r1;c<=n.r2;c++){for(h=0,s=n.g1;s<=n.g2;s++)for(f=n.b1;f<=n.b2;f++)l=e(c,s,f),h+=t[l]||0;p+=h,v[c]=p}else if(u==a)for(c=n.g1;c<=n.g2;c++){for(h=0,s=n.r1;s<=n.r2;s++)for(f=n.b1;f<=n.b2;f++)l=e(s,c,f),h+=t[l]||0;p+=h,v[c]=p}else for(c=n.b1;c<=n.b2;c++){for(h=0,s=n.r1;s<=n.r2;s++)for(f=n.g1;f<=n.g2;f++)l=e(s,f,c),h+=t[l]||0;p+=h,v[c]=p}return v.forEach(function(e,t){g[t]=p-e}),function(e){var t,r,o,a,i,u=e+"1",s=e+"2",f=0;for(c=n[u];c<=n[s];c++)if(v[c]>p/2){for(o=n.copy(),a=n.copy(),t=c-n[u],r=n[s]-c,i=t<=r?Math.min(n[s]-1,~~(c+r/2)):Math.max(n[u],~~(c-1-t/2));!v[i];)i++;for(f=g[i];!f&&v[i-1];)f=g[--i];return o[s]=i,a[u]=o[s]+1,[o,a]}}(u==o?"r":u==a?"g":"b")}}function c(e,n){function c(e,t){for(var r,n=1,o=0;o<h;)if(r=e.pop(),r.count()){var a=u(s,r),i=a[0],c=a[1];if(!i)return;if(e.push(i),c&&(e.push(c),n++),n>=t)return;if(o++>h)return}else e.push(r),o++}if(!e.length||n<2||n>256)return!1;var s=a(e),f=0;s.forEach(function(){f++});var p=i(e,s),v=new t(function(e,t){return r.naturalOrder(e.count(),t.count())});v.push(p),c(v,l*n);for(var g=new t(function(e,t){return r.naturalOrder(e.count()*e.volume(),t.count()*t.volume())});v.size();)g.push(v.pop());c(g,n-g.size());for(var d=new o;g.size();)d.push(g.pop());return d}var s=5,f=8-s,h=1e3,l=.75;return n.prototype={volume:function(e){var t=this;return t._volume&&!e||(t._volume=(t.r2-t.r1+1)*(t.g2-t.g1+1)*(t.b2-t.b1+1)),t._volume},count:function(t){var r=this,n=r.histo;if(!r._count_set||t){var o,a,i,u,c=0;for(a=r.r1;a<=r.r2;a++)for(i=r.g1;i<=r.g2;i++)for(u=r.b1;u<=r.b2;u++)o=e(a,i,u),c+=n[o]||0;r._count=c,r._count_set=!0}return r._count},copy:function(){var e=this;return new n(e.r1,e.r2,e.g1,e.g2,e.b1,e.b2,e.histo)},avg:function(t){var r=this,n=r.histo;if(!r._avg||t){var o,a,i,u,c,f=0,h=1<<8-s,l=0,p=0,v=0;for(a=r.r1;a<=r.r2;a++)for(i=r.g1;i<=r.g2;i++)for(u=r.b1;u<=r.b2;u++)c=e(a,i,u),o=n[c]||0,f+=o,l+=o*(a+.5)*h,p+=o*(i+.5)*h,v+=o*(u+.5)*h;r._avg=f?[~~(l/f),~~(p/f),~~(v/f)]:[~~(h*(r.r1+r.r2+1)/2),~~(h*(r.g1+r.g2+1)/2),~~(h*(r.b1+r.b2+1)/2)]}return r._avg},contains:function(e){var t=this,r=e[0]>>f,n=e[1]>>f,o=e[2]>>f;return r>=t.r1&&r<=t.r2&&n>=t.g1&&n<=t.g2&&o>=t.b1&&o<=t.b2}},o.prototype={push:function(e){this.vboxes.push({vbox:e,color:e.avg()})},palette:function(){return this.vboxes.map(function(e){return e.color})},size:function(){return this.vboxes.size()},map:function(e){for(var t=this.vboxes,r=0;r<t.size();r++)if(t.peek(r).vbox.contains(e))return t.peek(r).color;return this.nearest(e)},nearest:function(e){for(var t,r,n,o=this.vboxes,a=0;a<o.size();a++)((r=Math.sqrt(Math.pow(e[0]-o.peek(a).color[0],2)+Math.pow(e[1]-o.peek(a).color[1],2)+Math.pow(e[2]-o.peek(a).color[2],2)))<t||void 0===t)&&(t=r,n=o.peek(a).color);return n},forcebw:function(){var e=this.vboxes;e.sort(function(e,t){return r.naturalOrder(r.sum(e.color),r.sum(t.color))});var t=e[0].color;t[0]<5&&t[1]<5&&t[2]<5&&(e[0].color=[0,0,0]);var n=e.length-1,o=e[n].color;o[0]>251&&o[1]>251&&o[2]>251&&(e[n].color=[255,255,255])}},{quantize:c}}();return e.prototype.getColor=function(e,t){return this.getPalette(e,5,t)[0]},e.prototype.getPalette=function(e,r,o){(void 0===r||r<2||r>256)&&(r=10),(void 0===o||o<1)&&(o=10);for(var a,i,u,c,s=new t(e),f=s.getImageData(),h=f.data,l=s.getPixelCount(),p=[],v=0;v<l;v+=o)a=4*v,i=h[a+0],u=h[a+1],c=h[a+2],h[a+3]>=125&&(i>250&&u>250&&c>250||p.push([i,u,c]));var g=n.quantize(p,r),d=g?g.palette():null;return s.removeCanvas(),d},e.prototype.getColorFromUrl=function(e,t,r){sourceImage=document.createElement("img");var n=this;sourceImage.addEventListener("load",function(){t(n.getPalette(sourceImage,5,r)[0],e)}),sourceImage.src=e},e.prototype.getImageData=function(e,t){xhr=new XMLHttpRequest,xhr.open("GET",e,!0),xhr.responseType="arraybuffer",xhr.onload=function(e){if(200==this.status){uInt8Array=new Uint8Array(this.response),r=uInt8Array.length,binaryString=new Array(r);for(var r=0;r<uInt8Array.length;r++)binaryString[r]=String.fromCharCode(uInt8Array[r]);data=binaryString.join(""),base64=window.btoa(data),t("data:image/png;base64,"+base64)}},xhr.send()},e.prototype.getColorAsync=function(e,t,r){var n=this;this.getImageData(e,function(e){sourceImage=document.createElement("img"),sourceImage.addEventListener("load",function(){t(n.getPalette(sourceImage,5,r)[0],this)}),sourceImage.src=e})},e}();return e}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.palettify=e()}(this,function(){"use strict";function t(t){switch(typeof t){case"string":case"number":case"boolean":return t;case"object":try{return null===t?"":JSON.stringify(t)}catch(t){return"{...}"}default:return""}}function e(t,n,r){if(void 0===r&&(r=0),"object"!=typeof t||null===t||void 0===t)return"";var o=n[r],a=t[o];return r===n.length-1?a:e(a,n,++r)}function n(t,n){return e(n,t.split("."))}function r(e,r,o){return void 0===r&&(r={}),void 0===o&&(o=n),"string"!=typeof e?e:e.replace(v,function(e,a){try{return t(o(a,r))}catch(e){return t(n(a,r))}})}function o(t){return!!t&&"object"==typeof t}function a(t){var e=Object.prototype.toString.call(t);return"[object RegExp]"!==e&&"[object Date]"!==e}function i(t){return Array.isArray(t)?[]:{}}function s(t,e){return e&&!0===e.clone&&y(t)?l(i(t),t,e):t}function c(t,e,n){var r=t.slice();return e.forEach(function(e,o){void 0===r[o]?r[o]=s(e,n):y(e)?r[o]=l(t[o],e,n):-1===t.indexOf(e)&&r.push(s(e,n))}),r}function u(t,e,n){var r={};return y(t)&&Object.keys(t).forEach(function(e){r[e]=s(t[e],n)}),Object.keys(e).forEach(function(o){y(e[o])&&t[o]?r[o]=l(t[o],e[o],n):r[o]=s(e[o],n)}),r}function l(t,e,n){var r=Array.isArray(e),o=n||{arrayMerge:c},a=o.arrayMerge||c;return r?Array.isArray(t)?a(t,e,n):s(e,n):u(t,e,n)}function f(){function t(t,e){var n=v(t);return new Promise(function(t){f(n)?t((new p).getPalette(n,e)):n.onload=function(){t((new p).getPalette(n,e))}})}function e(t,e){return t.map(function(t,r){var o=1;return Array.isArray(e)&&e[r]&&(o=e[r]),n(t,o)})}function n(t,e){return"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function r(t){var e=t.target,n=t.styles,r=t.palette,o=t.staticCallback;void 0===o&&(o=null);var a=t.obj;for(var i in n)n.hasOwnProperty(i)&&(e.style[i]=h(n[i],r));o&&o.call(e,a,E.options)}function o(t,e,n,r){for(var o in e)e.hasOwnProperty(o)&&!n.hasOwnProperty(o)?t.style[o]="":n.hasOwnProperty(o)&&(t.style[o]=h(n[o],r))}function a(t){E.options=d(m,t,{clone:!0,arrayMerge:g}),Object.keys(E.options).forEach(function(t){if(E.options[t]instanceof Error)throw E.options[t]})}function i(t){return(299*t[0]+587*t[1]+114*t[2])/1e3}function s(t){return i(t)<170}function c(t,e){return t.map(function(t){return s(t)?e.light:e.dark})}function u(t){var e=document.createElement("a");return e.href=t,e}function l(t){return document.location.host!==u(t).host}function f(t){return!!t.complete&&(void 0===t.naturalWidth||0!==t.naturalWidth)}function v(t){var e=t,n="IMG"!==e.tagName,r=n?t.style.backgroundImage.replace("url(","").replace(")","").replace(/"/gi,""):t.src,o=l(r);if(!e)throw Error("Target is not an element",e);if(n||o&&!e.crossOrigin){if(n&&!e.style.backgroundImage)throw Error("Tag provided is not an image and does not have a background-image style attached to it.");e=new Image(t.naturalWidth,t.naturalHeight),o&&(e.crossOrigin="anonymous"),e.src=r}return e}function g(t,e,n){return e}var y=!1,b=null,m={selector:Error("Please provide \u0430 selector in your options."),eventTarget:Error("Please provide an eventTarget as a parent for your image in the options."),image:Error("Please provide an image to sample."),styleTarget:null,contrastColors:{light:"#fff",dark:"#000"},activeClass:"palettify--active",readyClass:"palettify--ready",colorsToExtract:3,enterEvent:"mouseenter",leaveEvent:"mouseleave",styles:{opacities:[.5,.5,.5],static:{},dynamic:{}},staticCallback:null,beforeEnterCallback:null,afterEnterCallback:null,beforeLeaveCallback:null,afterLeaveCallback:null,onReadyCallback:null},E={options:{},data:[],collectElements:function(){var t="";if(!(b="string"==typeof E.options.selector?document.querySelector(E.options.selector):E.options.selector))throw new Error("Selector "+E.options.selector+" does not exist");t=b.querySelectorAll(E.options.eventTarget),[].slice.call(t,0).forEach(function(t){var e=t.querySelector(E.options.image),n=!0===E.options.styleTarget?t:t.querySelector(E.options.styleTarget||E.options.image),r={eventTarget:t,styleTarget:n,image:e,palette:{original:[],rgb:[],rgba:[]}};E.data.push(r)})},extractColorsAndAttachStyles:function(n){void 0===n&&(n=!1);var o=[];E.data.forEach(function(n,a){o[a]=t(n.image,E.options.colorsToExtract).then(function(t){n.palette.original=t,n.palette.rgb=e(n.palette.original,[]),n.palette.rgba=e(n.palette.original,E.options.styles.opacities),n.palette.contrastColors=c(n.palette.original,E.options.contrastColors),r({target:n.styleTarget,styles:E.options.styles.static,palette:n.palette,staticCallback:E.options.staticCallback,obj:n})})}),n||Promise.all(o).then(function(t){b.classList.add(E.options.readyClass),"function"==typeof E.options.onReadyCallback&&E.options.onReadyCallback.call(E,E)})},generateEnterHandler:function(t){return function(e){t.styleTarget&&("function"==typeof E.options.beforeEnterCallback&&E.options.beforeEnterCallback.call(t.styleTarget,t,E.options,e),t.styleTarget.classList.add(E.options.activeClass),r({target:t.styleTarget,styles:E.options.styles.dynamic,palette:t.palette,obj:t}),"function"==typeof E.options.afterEnterCallback&&E.options.afterEnterCallback.call(t.styleTarget,t,E.options,e))}},generateLeaveHandler:function(t){return function(e){var n=t.styleTarget;n&&("function"==typeof E.options.beforeLeaveCallback&&E.options.beforeLeaveCallback.call(t.styleTarget,t,E.options,e),n.classList.remove(E.options.activeClass),o(t.styleTarget,E.options.styles.dynamic,E.options.styles.static,t.palette),"function"==typeof E.options.afterLeaveCallback&&E.options.afterLeaveCallback.call(t.styleTarget,t,E.options,e))}},attachEventListeners:function(){var t=E.options,e=t.enterEvent,n=t.leaveEvent;e=Array.isArray(e)?e:[e],n=Array.isArray(n)?n:[n],E.data.forEach(function(t){t.enterHandler=E.generateEnterHandler(t),t.leaveHandler=E.generateLeaveHandler(t),e.forEach(function(e){t.eventTarget.addEventListener(e,t.enterHandler,!1)}),n.forEach(function(e){t.eventTarget.addEventListener(e,t.leaveHandler,!1)})})},detachEventListeners:function(){var t=E.options,e=t.enterEvent,n=t.leaveEvent;e=Array.isArray(e)?e:[e],n=Array.isArray(n)?n:[n],E.data.forEach(function(t){e.forEach(function(e){t.eventTarget.removeEventListener(e,t.enterHandler,!1)}),n.forEach(function(e){t.eventTarget.removeEventListener(e,t.leaveHandler,!1)})})},init:function(t){if(y)throw new Error("Palettify is already initialized");return a(t),E.collectElements(),E.extractColorsAndAttachStyles(),E.attachEventListeners(),y=!0,E},destroy:function(t){void 0===t&&(t=!0),E.detachEventListeners(),t&&E.cleanUp(),E.data=[],y=!1},reInit:function(){E.destroy(),E.init(E.options)},cleanUp:function(){E.data.forEach(function(t){for(var e in E.options.styles.static)E.options.styles.static.hasOwnProperty(e)&&(t.styleTarget.style[e]="")})},setOptions:function(t,e){void 0===e&&(e=!0),E.options=Object.assign({},E.options,t),e&&E.reInit()},isInitialized:function(){return y}};return E}var p=function(){var t=function(){},e=function(t){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),document.body.appendChild(this.canvas),this.width=this.canvas.width=t.width,this.height=this.canvas.height=t.height,this.context.drawImage(t,0,0,this.width,this.height)};e.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},e.prototype.update=function(t){this.context.putImageData(t,0,0)},e.prototype.getPixelCount=function(){return this.width*this.height},e.prototype.getImageData=function(){return this.context.getImageData(0,0,this.width,this.height)},e.prototype.removeCanvas=function(){this.canvas.parentNode.removeChild(this.canvas)};var n={map:function(t,e){var n={};return e?t.map(function(t,r){return n.index=r,e.call(n,t)}):t.slice()},naturalOrder:function(t,e){return t<e?-1:t>e?1:0},sum:function(t,e){var n={};return t.reduce(e?function(t,r,o){return n.index=o,t+e.call(n,r)}:function(t,e){return t+e},0)},max:function(t,e){return Math.max.apply(null,e?n.map(t,e):t)}},r=function(){function t(t,e,n){return(t<<2*u)+(e<<u)+n}function e(t){function e(){n.sort(t),r=!0}var n=[],r=!1;return{push:function(t){n.push(t),r=!1},peek:function(t){return r||e(),void 0===t&&(t=n.length-1),n[t]},pop:function(){return r||e(),n.pop()},size:function(){return n.length},map:function(t){return n.map(t)},debug:function(){return r||e(),n}}}function r(t,e,n,r,o,a,i){var s=this;s.r1=t,s.r2=e,s.g1=n,s.g2=r,s.b1=o,s.b2=a,s.histo=i}function o(){this.vboxes=new e(function(t,e){return n.naturalOrder(t.vbox.count()*t.vbox.volume(),e.vbox.count()*e.vbox.volume())})}function a(e){var n,r,o,a,i=1<<3*u,s=new Array(i);return e.forEach(function(e){r=e[0]>>l,o=e[1]>>l,a=e[2]>>l,n=t(r,o,a),s[n]=(s[n]||0)+1}),s}function i(t,e){var n,o,a,i=1e6,s=0,c=1e6,u=0,f=1e6,p=0;return t.forEach(function(t){n=t[0]>>l,o=t[1]>>l,a=t[2]>>l,n<i?i=n:n>s&&(s=n),o<c?c=o:o>u&&(u=o),a<f?f=a:a>p&&(p=a)}),new r(i,s,c,u,f,p,e)}function s(e,r){if(r.count()){var o=r.r2-r.r1+1,a=r.g2-r.g1+1,i=r.b2-r.b1+1,s=n.max([o,a,i]);if(1==r.count())return[r.copy()];var c,u,l,f,p,v=0,g=[],h=[];if(s==o)for(c=r.r1;c<=r.r2;c++){for(f=0,u=r.g1;u<=r.g2;u++)for(l=r.b1;l<=r.b2;l++)p=t(c,u,l),f+=e[p]||0;v+=f,g[c]=v}else if(s==a)for(c=r.g1;c<=r.g2;c++){for(f=0,u=r.r1;u<=r.r2;u++)for(l=r.b1;l<=r.b2;l++)p=t(u,c,l),f+=e[p]||0;v+=f,g[c]=v}else for(c=r.b1;c<=r.b2;c++){for(f=0,u=r.r1;u<=r.r2;u++)for(l=r.g1;l<=r.g2;l++)p=t(u,l,c),f+=e[p]||0;v+=f,g[c]=v}return g.forEach(function(t,e){h[e]=v-t}),function(t){var e,n,o,a,i,s=t+"1",u=t+"2",l=0;for(c=r[s];c<=r[u];c++)if(g[c]>v/2){for(o=r.copy(),a=r.copy(),e=c-r[s],n=r[u]-c,i=e<=n?Math.min(r[u]-1,~~(c+n/2)):Math.max(r[s],~~(c-1-e/2));!g[i];)i++;for(l=h[i];!l&&g[i-1];)l=h[--i];return o[u]=i,a[s]=o[u]+1,[o,a]}}(s==o?"r":s==a?"g":"b")}}function c(t,r){function c(t,e){for(var n,r=1,o=0;o<f;)if(n=t.pop(),n.count()){var a=s(u,n),i=a[0],c=a[1];if(!i)return;if(t.push(i),c&&(t.push(c),r++),r>=e)return;if(o++>f)return}else t.push(n),o++}if(!t.length||r<2||r>256)return!1;var u=a(t),l=0;u.forEach(function(){l++});var v=i(t,u),g=new e(function(t,e){return n.naturalOrder(t.count(),e.count())});g.push(v),c(g,p*r);for(var h=new e(function(t,e){return n.naturalOrder(t.count()*t.volume(),e.count()*e.volume())});g.size();)h.push(g.pop());c(h,r-h.size());for(var y=new o;h.size();)y.push(h.pop());return y}var u=5,l=8-u,f=1e3,p=.75;return r.prototype={volume:function(t){var e=this;return e._volume&&!t||(e._volume=(e.r2-e.r1+1)*(e.g2-e.g1+1)*(e.b2-e.b1+1)),e._volume},count:function(e){var n=this,r=n.histo;if(!n._count_set||e){var o,a,i,s,c=0;for(a=n.r1;a<=n.r2;a++)for(i=n.g1;i<=n.g2;i++)for(s=n.b1;s<=n.b2;s++)o=t(a,i,s),c+=r[o]||0;n._count=c,n._count_set=!0}return n._count},copy:function(){var t=this;return new r(t.r1,t.r2,t.g1,t.g2,t.b1,t.b2,t.histo)},avg:function(e){var n=this,r=n.histo;if(!n._avg||e){var o,a,i,s,c,l=0,f=1<<8-u,p=0,v=0,g=0;for(a=n.r1;a<=n.r2;a++)for(i=n.g1;i<=n.g2;i++)for(s=n.b1;s<=n.b2;s++)c=t(a,i,s),o=r[c]||0,l+=o,p+=o*(a+.5)*f,v+=o*(i+.5)*f,g+=o*(s+.5)*f;n._avg=l?[~~(p/l),~~(v/l),~~(g/l)]:[~~(f*(n.r1+n.r2+1)/2),~~(f*(n.g1+n.g2+1)/2),~~(f*(n.b1+n.b2+1)/2)]}return n._avg},contains:function(t){var e=this,n=t[0]>>l,r=t[1]>>l,o=t[2]>>l;return n>=e.r1&&n<=e.r2&&r>=e.g1&&r<=e.g2&&o>=e.b1&&o<=e.b2}},o.prototype={push:function(t){this.vboxes.push({vbox:t,color:t.avg()})},palette:function(){return this.vboxes.map(function(t){return t.color})},size:function(){return this.vboxes.size()},map:function(t){for(var e=this.vboxes,n=0;n<e.size();n++)if(e.peek(n).vbox.contains(t))return e.peek(n).color;return this.nearest(t)},nearest:function(t){for(var e,n,r,o=this.vboxes,a=0;a<o.size();a++)((n=Math.sqrt(Math.pow(t[0]-o.peek(a).color[0],2)+Math.pow(t[1]-o.peek(a).color[1],2)+Math.pow(t[2]-o.peek(a).color[2],2)))<e||void 0===e)&&(e=n,r=o.peek(a).color);return r},forcebw:function(){var t=this.vboxes;t.sort(function(t,e){return n.naturalOrder(n.sum(t.color),n.sum(e.color))});var e=t[0].color;e[0]<5&&e[1]<5&&e[2]<5&&(t[0].color=[0,0,0]);var r=t.length-1,o=t[r].color;o[0]>251&&o[1]>251&&o[2]>251&&(t[r].color=[255,255,255])}},{quantize:c}}();return t.prototype.getColor=function(t,e){return this.getPalette(t,5,e)[0]},t.prototype.getPalette=function(t,n,o){(void 0===n||n<2||n>256)&&(n=10),(void 0===o||o<1)&&(o=10);for(var a,i,s,c,u=new e(t),l=u.getImageData(),f=l.data,p=u.getPixelCount(),v=[],g=0;g<p;g+=o)a=4*g,i=f[a+0],s=f[a+1],c=f[a+2],f[a+3]>=125&&(i>250&&s>250&&c>250||v.push([i,s,c]));var h=r.quantize(v,n),y=h?h.palette():null;return u.removeCanvas(),y},t.prototype.getColorFromUrl=function(t,e,n){sourceImage=document.createElement("img");var r=this;sourceImage.addEventListener("load",function(){e(r.getPalette(sourceImage,5,n)[0],t)}),sourceImage.src=t},t.prototype.getImageData=function(t,e){xhr=new XMLHttpRequest,xhr.open("GET",t,!0),xhr.responseType="arraybuffer",xhr.onload=function(t){if(200==this.status){uInt8Array=new Uint8Array(this.response),n=uInt8Array.length,binaryString=new Array(n);for(var n=0;n<uInt8Array.length;n++)binaryString[n]=String.fromCharCode(uInt8Array[n]);data=binaryString.join(""),base64=window.btoa(data),e("data:image/png;base64,"+base64)}},xhr.send()},t.prototype.getColorAsync=function(t,e,n){var r=this;this.getImageData(t,function(t){sourceImage=document.createElement("img"),sourceImage.addEventListener("load",function(){e(r.getPalette(sourceImage,5,n)[0],this)}),sourceImage.src=t})},t}(),v=/\{\{\s*(.*?)\s*\}\}/g,g=r,h=g,y=function(t){return o(t)&&a(t)};l.all=function(t,e){if(!Array.isArray(t)||t.length<2)throw new Error("first argument should be an array with at least two elements");return t.reduce(function(t,n){return l(t,n,e)})};var d=l;return f}); |
@@ -6,2 +6,3 @@ ## Explanation of Build Files | ||
- ES Module: palettify.esm.js | ||
- UMD: palettify.standalone.js | ||
@@ -15,1 +16,3 @@ ### Terms | ||
- **[ES Module](http://exploringjs.com/es6/ch_modules.html)**: ES module builds are intended for use with modern bundlers like [webpack 2](https://webpack.js.org) or [rollup](http://rollupjs.org/). The default file for these bundlers (`pkg.module`) is the Runtime only ES Module build (`palettify.esm.js`). | ||
- **Standalone**: UMD packaged module that can be used directly in the browser but requires the user to have [Color Thief](http://lokeshdhakar.com/projects/color-thief/) library added before Palettify. |
{ | ||
"name": "palettify", | ||
"description": "Configurable JavaScript plugin to extract image primary colors and apply cool effects to it.", | ||
"version": "0.0.0", | ||
"version": "1.0.0", | ||
"author": { | ||
@@ -13,2 +13,3 @@ "name": "Dobromir Hristov" | ||
"@mariotacke/color-thief": "^3.0.1", | ||
"autoprefixer": "^7.1.1", | ||
"babel-core": "^6.22.1", | ||
@@ -25,2 +26,3 @@ "babel-eslint": "^7.1.0", | ||
"conventional-github-releaser": "^1.1.3", | ||
"cz-conventional-changelog": "^2.0.0", | ||
"eslint": "^3.14.1", | ||
@@ -30,2 +32,4 @@ "eslint-config-standard": "^10.2.1", | ||
"eslint-plugin-import": "^2.3.0", | ||
"foundation-sites": "^6.4.1", | ||
"fs-extra": "^3.0.1", | ||
"gitbook-cli": "^2.3.0", | ||
@@ -45,5 +49,8 @@ "html-webpack-plugin": "^2.28.0", | ||
"karma-webpack": "^2.0.2", | ||
"micromustache": "^5.2.0", | ||
"mocha": "^3.2.0", | ||
"mocha-loader": "^1.1.1", | ||
"node-sass": "^4.5.3", | ||
"phantomjs-prebuilt": "^2.1.14", | ||
"postcss": "^6.0.6", | ||
"power-assert": "^1.4.2", | ||
@@ -58,5 +65,7 @@ "rollup": "^0.36.4", | ||
"rollup-plugin-replace": "^1.1.1", | ||
"rollup-plugin-sass": "^0.5.3", | ||
"rollup-watch": "^4.0.0", | ||
"sass-loader": "^6.0.6", | ||
"uglify-js": "^2.7.5", | ||
"webpack": "^2.2.0", | ||
"webpack": "^2.6.1", | ||
"webpack-dev-server": "^2.4.5", | ||
@@ -103,4 +112,11 @@ "yargs-parser": "^7.0.0" | ||
"dependencies": { | ||
"config-chain": "^1.1.11" | ||
"config-chain": "^1.1.11", | ||
"deepmerge": "^1.4.4", | ||
"jsonfile": "^3.0.0" | ||
}, | ||
"config": { | ||
"commitizen": { | ||
"path": "./node_modules/cz-conventional-changelog" | ||
} | ||
} | ||
} |
@@ -6,4 +6,5 @@ # Palettify | ||
[![npm](https://img.shields.io/npm/v/palettify.svg)](https://www.npmjs.com/package/palettify) | ||
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) | ||
Configurable JavaScript plugin to extract image primary color and apply beautiful effects to it. | ||
Configurable JavaScript plugin to extract image primary colors and apply cool effects to it. | ||
@@ -10,0 +11,0 @@ |
529
src/index.js
import ColorThief from '@mariotacke/color-thief' | ||
import { render } from 'micromustache' | ||
import merge from 'deepmerge' | ||
/** | ||
* palettify | ||
* @param {Object} opts | ||
* @param {String | NodeList} opts.imageTarget | ||
* @param {String | NodeList} opts.hoverTarget | ||
* @param {String} opts.attachBoxShadowTo | ||
* @param {String | Number} opts.opacity | ||
* @param {String} opts.opacitySecondary | ||
* @param {String} opts.colorIndexToUse | ||
* @param {String} opts.boxShadowTemplate - Provide a boxShadow template to apply. '0 2px 2px {color}, 3px 3px {colorSecondary}' | ||
* Creates a palettify instance | ||
* @module palettify | ||
* @return palettify | ||
*/ | ||
function palettify () { | ||
/** | ||
* Extract the colors from image tag or Background-image inline style | ||
* @param {HTMLElement} paletteTarget - The palette target to get the colors form | ||
* @return {Array} Returns an object with RGB colors | ||
* @private | ||
*/ | ||
function __extractColors (paletteTarget) { | ||
let image = paletteTarget | ||
if (!paletteTarget) throw Error('Target is not an element', paletteTarget) | ||
// Our sample is not a img tag so we try to get its background image. | ||
if (paletteTarget.tagName !== 'IMG' && paletteTarget.style.backgroundImage) Error('Tag provided is not an image and does not have a background-image style attached to it.') | ||
// Its not an IMG tag so we try to crate one under the hood. | ||
if (paletteTarget.tagName !== 'IMG') { | ||
image = new Image(paletteTarget.offsetWidth, paletteTarget.offsetHeight) | ||
image.src = paletteTarget.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') | ||
} | ||
return new ColorThief().getPalette(image, 3) | ||
} | ||
function createPalettify () { | ||
let | ||
isInitialized = false, | ||
__selector = null | ||
/** | ||
* Returns the rgba color from current color with applied opacity | ||
* @param {Array} color | ||
* @param {String | Number} opacity | ||
* @return {string} | ||
* @private | ||
* @typedef {Object} paletteObj | ||
* @property {HTMLElement} eventTarget | ||
* @property {HTMLElement} styleTarget | ||
* @property {HTMLElement} image | ||
* @property {PaletteObject} palette | ||
* @property {Function} enterHandler | ||
* @property {Function} leaveHandler | ||
*/ | ||
function __getRgbaColor (color, opacity) { | ||
const | ||
r = color[0], | ||
g = color[1], | ||
b = color[2] | ||
return `rgba(${r}, ${g}, ${b}, ${opacity})` | ||
} | ||
/** | ||
* Adds boxShadow to an element. | ||
* Required the color and the element to attach to | ||
* @param {Array} colors - Array of RGB Colors | ||
* @param {Object} obj - Object that holds the hoverTarget and image | ||
* @param {HTMLElement} obj.hoverTarget - Reference to the hoverTarget dom element | ||
* @param {HTMLElement} obj.image - Reference to the image dom element | ||
* @param {Array} obj.colors - Array of RGB Colors | ||
* @param {String} obj.rgbaColor - rgba transformed color with opacity in mind. | ||
* @param {String} obj.rgbaColorSecondary - rgba transformed color with secondary opacity in mind. | ||
* @private | ||
* @typedef {Object} PaletteObject | ||
* @property {Array} original | ||
* @property {Array} rgb | ||
* @property {Array} rgba | ||
*/ | ||
function __addBoxShadowToElementData (colors, obj) { | ||
const | ||
{opacity, opacitySecondary, boxShadowTemplate} = self.options, | ||
color = colors[self.options.colorIndexToUse], | ||
rgbaColor = __getRgbaColor(color, opacity), | ||
rgbaColorSecondary = __getRgbaColor(color, opacitySecondary) | ||
let boxShadow | ||
if (!boxShadowTemplate) { | ||
boxShadow = `0 2px 2px ${rgbaColor}, 0 4px 4px ${rgbaColor}, 0 8px 8px ${rgbaColor}, 0 16px 16px ${rgbaColor}, 0 32px 32px ${rgbaColorSecondary}, 0 64px 64px ${rgbaColorSecondary}` | ||
} else { | ||
boxShadow = boxShadowTemplate.replace('{color}', rgbaColor).replace('{colorSecondary}', rgbaColorSecondary) | ||
} | ||
obj.colors = colors | ||
obj.rgbaColor = rgbaColor | ||
obj.rgbaColorSecondary = rgbaColorSecondary | ||
obj.boxShadow = boxShadow | ||
} | ||
let isInitialized = false | ||
const | ||
/** | ||
* Default options to extend | ||
* @typedef {Object} DefaultOptions | ||
* @property {String|HTMLElement} selector - The element that holds all your images and eventTargets. | ||
* @property {String} eventTarget - The event target to attach event listeners to | ||
* @property {String} image - The image to sample | ||
* @property {String} styleTarget - The element to apply styling to. Defaults to image | ||
* @property {Object} contrastColors - Light and Dark colors, based on brightness of each color in the palette | ||
* @property {String} contrastColors.light - Light inverted color | ||
* @property {String} contrastColors.dark - Dark inverted color | ||
* @property {String} activeClass - CSS class to apply on each enterEvent | ||
* @property {String} readyClass - CSS class to apply when palettify is ready | ||
* @property {Number} colorsToExtract - Colors to extract | ||
* @property {String | Array} enterEvent - Event or Array of events to apply listeners to for each enterCallback | ||
* @property {string | Array} leaveEvent - Event or Array of events to apply listeners to for each leaveCallback | ||
* @property {Object} styles - Collection of static and dynamic styles | ||
* @property {Array<number>} styles.opacities - Array of opacities | ||
* @property {Object} styles.static - Object containing valid css styles to apply to styleTarget on ready | ||
* @property {Object} styles.dynamic - Object containing valid css styles to apply to styleTarget on each enterEvent | ||
* @property {Function} beforeEnterCallback - Callback called before the enter event | ||
* @property {Function} afterEnterCallback - Callback called after the enter event | ||
* @property {Function} beforeLeaveCallback - Callback called before the leave event | ||
* @property {Function} afterLeaveCallback - Callback called after the enter event | ||
* @property {Function} onReadyCallback - Callback called after palettify is ready. | ||
*/ | ||
__defaults = { | ||
hoverTarget: Error('Please provide hoverTarget'), | ||
imageTarget: Error('Please provide ImageSrc'), | ||
attachBoxShadowTo: null, | ||
opacity: 0.2, | ||
opacitySecondary: 0.2, | ||
hoverClass: 'box-shadow-attached', | ||
colorIndexToUse: 0, | ||
selector: Error('Please provide а selector in your options.'), | ||
eventTarget: Error('Please provide an eventTarget as a parent for your image in the options.'), | ||
image: Error('Please provide an image to sample.'), | ||
styleTarget: null, | ||
contrastColors: { | ||
light: '#fff', | ||
dark: '#000' | ||
}, | ||
activeClass: 'palettify--active', | ||
readyClass: 'palettify--ready', | ||
colorsToExtract: 3, | ||
enterEvent: 'mouseenter', | ||
leaveEvent: 'mouseleave', | ||
boxShadowTemplate: '' | ||
styles: { | ||
opacities: [0.5, 0.5, 0.5], | ||
static: {}, | ||
dynamic: {} | ||
}, | ||
staticCallback: null, | ||
beforeEnterCallback: null, | ||
afterEnterCallback: null, | ||
beforeLeaveCallback: null, | ||
afterLeaveCallback: null, | ||
onReadyCallback: null | ||
}, | ||
/** | ||
* @typedef {Object} palettify | ||
* @type Object | ||
* @property {DefaultOptions} options | ||
* @property {Array} data | ||
* @property {function} collectElements | ||
* @property {function} extractColorsAndAttachStyles | ||
* @property {function} generateEnterHandler | ||
* @property {function} generateLeaveHandler | ||
* @property {function} attachEventListeners | ||
* @property {function} detachEventListeners | ||
* @property {function} init | ||
* @property {function} destroy | ||
* @property {function} reInit | ||
* @property {function} cleanUp | ||
* @property {function} setOptions | ||
* @property {function} isInitialized | ||
*/ | ||
self = { | ||
/** | ||
* Palettify options | ||
* @name palettify#options | ||
* @type DefaultOptions | ||
*/ | ||
options: {}, | ||
elements: [], | ||
/** | ||
* Gather all needed elements and push into an the `elements` array | ||
* Holds a collection of {@link paletteObj} objects. | ||
* @type Array<paletteObj> | ||
* @name palettify#data | ||
*/ | ||
data: [], | ||
/** | ||
* Gather all needed elements and push into an the {@see palettify#data} | ||
* @type function | ||
* @name palettify#collectElements | ||
*/ | ||
collectElements () { | ||
let hoverTargetsCollection = self.options.hoverTarget | ||
if (typeof hoverTargetsCollection === 'string') { | ||
hoverTargetsCollection = document.querySelectorAll(hoverTargetsCollection) | ||
} | ||
if (!hoverTargetsCollection.length) return | ||
for (let i = 0; i < hoverTargetsCollection.length; i++) { | ||
let | ||
eventTargetsCollection = '' | ||
__selector = typeof self.options.selector === 'string' ? document.querySelector(self.options.selector) : self.options.selector | ||
if (!__selector) { throw new Error('Selector ' + self.options.selector + ' does not exist') } | ||
eventTargetsCollection = __selector.querySelectorAll(self.options.eventTarget); | ||
[].slice.call(eventTargetsCollection, 0).forEach((eventTarget) => { | ||
const | ||
element = hoverTargetsCollection[i], | ||
image = element.querySelector(self.options.imageTarget), | ||
image = eventTarget.querySelector(self.options.image), | ||
styleTarget = self.options.styleTarget === true ? eventTarget : eventTarget.querySelector(self.options.styleTarget || self.options.image), | ||
// Create the main object it self. | ||
obj = { | ||
hoverTarget: element, | ||
image | ||
eventTarget, | ||
styleTarget, | ||
image, | ||
palette: { | ||
original: [], | ||
rgb: [], | ||
rgba: [] | ||
} | ||
} | ||
self.elements.push(obj) | ||
} | ||
self.data.push(obj) | ||
}) | ||
}, | ||
/** | ||
* Attaches boxShadow to the collection of Images | ||
* Extracts colors and attaches static styles to each styleTarget {@see palettify#options.styleTarget} | ||
* Adds ready class and calls onReadyCallback when done | ||
* @function | ||
* @name palettify#extractColorsAndAttachStyles | ||
*/ | ||
attachBoxShadowToCollection () { | ||
self.elements.forEach(obj => { | ||
const color = __extractColors(obj.image) | ||
__addBoxShadowToElementData(color, obj) | ||
extractColorsAndAttachStyles (skipCallbacks = false) { | ||
let promises = [] | ||
self.data.forEach((obj, index) => { | ||
promises[index] = __extractColors(obj.image, self.options.colorsToExtract).then(colors => { | ||
obj.palette.original = colors | ||
obj.palette.rgb = __opacifyPalette(obj.palette.original, []) | ||
obj.palette.rgba = __opacifyPalette(obj.palette.original, self.options.styles.opacities) | ||
obj.palette.contrastColors = __getInvertedColors(obj.palette.original, self.options.contrastColors) | ||
__attachStylesToElement({ | ||
target: obj.styleTarget, | ||
styles: self.options.styles.static, | ||
palette: obj.palette, | ||
staticCallback: self.options.staticCallback, | ||
obj | ||
}) | ||
}) | ||
}) | ||
if (!skipCallbacks) { | ||
Promise.all(promises).then(values => { | ||
__selector.classList.add(self.options.readyClass) | ||
typeof self.options.onReadyCallback === 'function' && self.options.onReadyCallback.call(self, self) | ||
}) | ||
} | ||
}, | ||
/** | ||
* Enter event listener callback | ||
* Adds the boxShadow to the target | ||
* @param event | ||
* Generates the enter event listener callback | ||
* Attaches dynamicStyles {@see palettify#options.styles.dynamic} to styleTarget | ||
* @function | ||
* @param {paletteObj} obj | ||
* @name palettify#generateEnterHandler | ||
* @return function | ||
*/ | ||
enterHandler (obj) { | ||
generateEnterHandler (obj) { | ||
return (event) => { | ||
const target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget) | ||
if (target) { | ||
target.classList.add(self.options.hoverClass) | ||
target.style.boxShadow = obj.boxShadow | ||
if (obj.styleTarget) { | ||
if (typeof self.options.beforeEnterCallback === 'function') self.options.beforeEnterCallback.call(obj.styleTarget, obj, self.options, event) | ||
obj.styleTarget.classList.add(self.options.activeClass) | ||
__attachStylesToElement({ | ||
target: obj.styleTarget, | ||
styles: self.options.styles.dynamic, | ||
palette: obj.palette, | ||
obj | ||
}) | ||
if (typeof self.options.afterEnterCallback === 'function') self.options.afterEnterCallback.call(obj.styleTarget, obj, self.options, event) | ||
} | ||
@@ -140,12 +197,17 @@ } | ||
/** | ||
* Leave Event listener callback | ||
* Removes the Box shadow from the target | ||
* @param event | ||
* Generates the leave event listener callback | ||
* Removes all dynamicStyles | ||
* @function | ||
* @param {paletteObj} obj | ||
* @name palettify#generateLeaveHandler | ||
* @return function | ||
*/ | ||
leaveHandler (obj) { | ||
generateLeaveHandler (obj) { | ||
return (event) => { | ||
const target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget) | ||
const target = obj.styleTarget | ||
if (target) { | ||
target.classList.remove(self.options.hoverClass) | ||
target.style.boxShadow = '' | ||
if (typeof self.options.beforeLeaveCallback === 'function') self.options.beforeLeaveCallback.call(obj.styleTarget, obj, self.options, event) | ||
target.classList.remove(self.options.activeClass) | ||
__removeDynamicStylesFromElement(obj.styleTarget, self.options.styles.dynamic, self.options.styles.static, obj.palette) | ||
if (typeof self.options.afterLeaveCallback === 'function') self.options.afterLeaveCallback.call(obj.styleTarget, obj, self.options, event) | ||
} | ||
@@ -155,22 +217,44 @@ } | ||
/** | ||
* Attaches Event listeners to the hoverTargets | ||
* Attaches Event listeners to the eventTargets | ||
* @function | ||
* @name palettify#attachEventListeners | ||
*/ | ||
attachEventListeners () { | ||
const {enterEvent, leaveEvent} = self.options | ||
self.elements.forEach(obj => { | ||
obj.enterHandler = self.enterHandler(obj) | ||
obj.leaveHandler = self.leaveHandler(obj) | ||
obj.hoverTarget.addEventListener(enterEvent, obj.enterHandler, false) | ||
obj.hoverTarget.addEventListener(leaveEvent, obj.leaveHandler, false) | ||
let {enterEvent, leaveEvent} = self.options | ||
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent | ||
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent | ||
self.data.forEach(obj => { | ||
obj.enterHandler = self.generateEnterHandler(obj) | ||
obj.leaveHandler = self.generateLeaveHandler(obj) | ||
enterEvent.forEach((event) => { | ||
obj.eventTarget.addEventListener(event, obj.enterHandler, false) | ||
}) | ||
leaveEvent.forEach((event) => { | ||
obj.eventTarget.addEventListener(event, obj.leaveHandler, false) | ||
}) | ||
}) | ||
}, | ||
/** | ||
* Detaches event listeners from hoverTargets | ||
* Detaches event listeners from eventTargets | ||
* @function | ||
* @name palettify#detachEventListeners | ||
*/ | ||
detachEventListeners () { | ||
const | ||
let | ||
{enterEvent, leaveEvent} = self.options | ||
self.elements.forEach(obj => { | ||
obj.hoverTarget.removeEventListener(enterEvent, obj.enterHandler, false) | ||
obj.hoverTarget.removeEventListener(leaveEvent, obj.leaveHandler, false) | ||
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent | ||
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent | ||
self.data.forEach(obj => { | ||
enterEvent.forEach((event) => { | ||
obj.eventTarget.removeEventListener(event, obj.enterHandler, false) | ||
}) | ||
leaveEvent.forEach((event) => { | ||
obj.eventTarget.removeEventListener(event, obj.leaveHandler, false) | ||
}) | ||
}) | ||
@@ -181,2 +265,6 @@ }, | ||
* Gets fired when creating Palettify in the first place | ||
* @constructs | ||
* @param {DefaultOptions} opts - Options to pass to palettify | ||
* @name palettify#init | ||
* @return palettify | ||
*/ | ||
@@ -186,7 +274,7 @@ init (opts) { | ||
self.options = Object.assign({}, __defaults, opts) | ||
__mergeOptions(opts) | ||
self.collectElements() | ||
// Attach boxShadow's to all images | ||
self.attachBoxShadowToCollection() | ||
// Collect colors and attach boxShadow's to all images | ||
self.extractColorsAndAttachStyles() | ||
// Attach event listeners to all hovered elements. | ||
@@ -201,6 +289,10 @@ self.attachEventListeners() | ||
* Destroys the Palettify and cleans up after it self. | ||
* @function | ||
* @name palettify#destroy | ||
* @param {Boolean} [cleanUp = true] | ||
*/ | ||
destroy () { | ||
destroy (cleanUp = true) { | ||
self.detachEventListeners() | ||
self.elements = [] | ||
cleanUp && self.cleanUp() | ||
self.data = [] | ||
isInitialized = false | ||
@@ -211,2 +303,4 @@ }, | ||
* Destroy and then reinitialize it. | ||
* @function | ||
* @name palettify#reInit | ||
*/ | ||
@@ -218,7 +312,21 @@ reInit () { | ||
/** | ||
* Cleans up the dom after the plugin is destroyed. Removes all styles.static | ||
* @function | ||
* @name palettify#cleanUp | ||
*/ | ||
cleanUp () { | ||
self.data.forEach(obj => { | ||
for (let prop in self.options.styles.static) { | ||
if (self.options.styles.static.hasOwnProperty(prop)) { | ||
obj.styleTarget.style[prop] = '' | ||
} | ||
} | ||
}) | ||
}, | ||
/** | ||
* Set new options. Merges then with the old ones. | ||
* Takes an options object and a reInit boolean. | ||
* reInit defaults to True | ||
* @param {Object} options - New options to override the old ones | ||
* @param {Boolean} reInit - Should the plugin reInit? Defaults to true | ||
* @param {Boolean} [reInit = true] - Should the plugin reInit? Defaults to true | ||
* @function | ||
* @name palettify#setOptions | ||
*/ | ||
@@ -231,2 +339,3 @@ setOptions (options, reInit = true) { | ||
* Is the plugin initialized | ||
* @name palettify#isInitialized | ||
* @return {boolean} | ||
@@ -239,5 +348,161 @@ */ | ||
/** | ||
* Extract the colors from image tag or Background-image inline style | ||
* @param {HTMLElement} paletteTarget - The palette target to get the colors form | ||
* @param {Number} colorsToExtract - Number of colors to extract | ||
* @return {Promise} Returns promise when image is loaded with an array of RGB colors | ||
* @private | ||
*/ | ||
function __extractColors (paletteTarget, colorsToExtract) { | ||
let image = __sanitizeImage(paletteTarget) | ||
return new Promise(resolve => { | ||
if (!isImageLoaded(image)) { | ||
image.onload = function () { | ||
resolve(new ColorThief().getPalette(image, colorsToExtract)) | ||
} | ||
} else { | ||
resolve(new ColorThief().getPalette(image, colorsToExtract)) | ||
} | ||
}) | ||
} | ||
/** | ||
* Transform all colors in the palette to RGBA colors using the supplied opacities in {@see palettify#options.opacities} | ||
* @param {Array} palette - Color palette of the current obj | ||
* @param {Array} opacities - Array of opacities for each color | ||
* @private | ||
*/ | ||
function __opacifyPalette (palette, opacities) { | ||
return palette.map((color, i) => { | ||
let opacity = 1 | ||
if (Array.isArray(opacities) && opacities[i]) { | ||
opacity = opacities[i] | ||
} | ||
return __getRgbaColor(color, opacity) | ||
}) | ||
} | ||
/** | ||
* Returns the rgba color with applied opacity | ||
* @param {Array} color | ||
* @param {String | Number} opacity | ||
* @return {string} | ||
* @private | ||
*/ | ||
function __getRgbaColor (color, opacity) { | ||
const | ||
r = color[0], | ||
g = color[1], | ||
b = color[2] | ||
return `rgba(${r}, ${g}, ${b}, ${opacity})` | ||
} | ||
/** | ||
* Adds styles to an element. | ||
* @param target | ||
* @param {Object} styles | ||
* @param palette | ||
* @param staticCallback | ||
* @param obj | ||
* @private | ||
*/ | ||
function __attachStylesToElement ({target, styles, palette, staticCallback = null, obj}) { | ||
for (const prop in styles) { | ||
if (styles.hasOwnProperty(prop)) { | ||
target.style[prop] = render(styles[prop], palette) | ||
} | ||
} | ||
staticCallback && staticCallback.call(target, obj, self.options) | ||
} | ||
/** | ||
* Removes all dynamic styles from an element. | ||
* If the staticStyle has the same prop as dynamicStyle, we set the prop to be the static style. | ||
* @param {HTMLElement} target - The Target to remove styles from | ||
* @param {Object} dynamicStyles - Dynamic styles to apply | ||
* @param {Object} staticStyles - Static styles to apply | ||
* @param {Array} palette - Array of available palettes | ||
* @private | ||
*/ | ||
function __removeDynamicStylesFromElement (target, dynamicStyles, staticStyles, palette) { | ||
for (const prop in dynamicStyles) { | ||
if (dynamicStyles.hasOwnProperty(prop) && !staticStyles.hasOwnProperty(prop)) { | ||
target.style[prop] = '' | ||
} else if (staticStyles.hasOwnProperty(prop)) { | ||
target.style[prop] = render(staticStyles[prop], palette) | ||
} | ||
} | ||
} | ||
/** | ||
* Merges the options and throws errors where necessary | ||
* @param {DefaultOptions} options | ||
* @private | ||
*/ | ||
function __mergeOptions (options) { | ||
self.options = merge(__defaults, options, {clone: true, arrayMerge: __arrayMerge}) | ||
Object.keys(self.options).forEach(opt => { | ||
if (self.options[opt] instanceof Error) { | ||
throw self.options[opt] | ||
} | ||
}) | ||
} | ||
function __getBrightness (rgb) { | ||
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 | ||
} | ||
function __isDark (color) { | ||
return __getBrightness(color) < 170 // 128 by default, but 170 gives better results | ||
} | ||
function __getInvertedColors (palette, colors) { | ||
return palette.map(color => __isDark(color) ? colors.light : colors.dark) | ||
} | ||
function __getUrl (url) { | ||
const el = document.createElement('a') | ||
el.href = url | ||
return el | ||
} | ||
function __isCORS (src) { | ||
return document.location.host !== __getUrl(src).host | ||
} | ||
function isImageLoaded (img) { | ||
if (!img.complete) return false | ||
if (typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0) { | ||
return false | ||
} | ||
// No other way of checking: assume it's ok. | ||
return true | ||
} | ||
function __sanitizeImage (imgElement) { | ||
let | ||
cachedImg = imgElement, | ||
isNotIMG = cachedImg.tagName !== 'IMG', | ||
src = isNotIMG ? imgElement.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') : imgElement.src, | ||
isCors = __isCORS(src) | ||
if (!cachedImg) throw Error('Target is not an element', cachedImg) | ||
// Our sample is not a img tag so we try to get its background image. | ||
if (isNotIMG || (isCors && !cachedImg.crossOrigin)) { | ||
if (isNotIMG && !cachedImg.style.backgroundImage) throw Error('Tag provided is not an image and does not have a background-image style attached to it.') | ||
cachedImg = new Image(imgElement.naturalWidth, imgElement.naturalHeight) | ||
isCors && (cachedImg.crossOrigin = 'anonymous') | ||
cachedImg.src = src | ||
} | ||
return cachedImg | ||
} | ||
function __arrayMerge (destArray, sourceArray, opts) { | ||
return sourceArray | ||
} | ||
return self | ||
} | ||
export default palettify | ||
export default createPalettify |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
112644
15
1897
0
28
3
56
1
+ Addeddeepmerge@^1.4.4
+ Addedjsonfile@^3.0.0
+ Addeddeepmerge@1.5.2(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedjsonfile@3.0.1(transitive)