element-resize-detector
Advanced tools
Comparing version 1.0.3 to 1.1.0
/*! | ||
* element-resize-detector 1.0.3 | ||
* element-resize-detector 1.1.0 | ||
* https://github.com/wnr/element-resize-detector | ||
@@ -23,36 +23,33 @@ * Licensed under MIT | ||
var batch; | ||
var batchSize; | ||
var topLevel; | ||
var bottomLevel; | ||
clearBatch(); | ||
var batch = Batch(); | ||
var asyncFrameHandler; | ||
var isProcessing = false; | ||
function addFunction(level, fn) { | ||
if(!fn) { | ||
fn = level; | ||
level = 0; | ||
if(!isProcessing && autoProcess && asyncProcess && batch.size() === 0) { | ||
// Since this is async, it is guaranteed to be executed after that the fn is added to the batch. | ||
// This needs to be done before, since we're checking the size of the batch to be 0. | ||
processBatchAsync(); | ||
} | ||
if(level > topLevel) { | ||
topLevel = level; | ||
} else if(level < bottomLevel) { | ||
bottomLevel = level; | ||
} | ||
batch.add(level, fn); | ||
} | ||
if(!batch[level]) { | ||
batch[level] = []; | ||
function processBatch() { | ||
// Save the current batch, and create a new batch so that incoming functions are not added into the currently processing batch. | ||
// Continue processing until the top-level batch is empty (functions may be added to the new batch while processing, and so on). | ||
isProcessing = true; | ||
while (batch.size()) { | ||
var processingBatch = batch; | ||
batch = Batch(); | ||
processingBatch.process(); | ||
} | ||
isProcessing = false; | ||
} | ||
if(autoProcess && asyncProcess && batchSize === 0) { | ||
processBatchAsync(); | ||
function forceProcessBatch(localAsyncProcess) { | ||
if (isProcessing) { | ||
return; | ||
} | ||
batch[level].push(fn); | ||
batchSize++; | ||
} | ||
function forceProcessBatch(localAsyncProcess) { | ||
if(localAsyncProcess === undefined) { | ||
@@ -74,14 +71,2 @@ localAsyncProcess = asyncProcess; | ||
function processBatch() { | ||
for(var level = bottomLevel; level <= topLevel; level++) { | ||
var fns = batch[level]; | ||
for(var i = 0; i < fns.length; i++) { | ||
var fn = fns[i]; | ||
fn(); | ||
} | ||
} | ||
clearBatch(); | ||
} | ||
function processBatchAsync() { | ||
@@ -100,3 +85,3 @@ asyncFrameHandler = requestFrame(processBatch); | ||
// var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.clearTimeout; | ||
var cancel = window.clearTimeout; | ||
var cancel = clearTimeout; | ||
return cancel(listener); | ||
@@ -107,3 +92,3 @@ } | ||
// var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || function(fn) { return window.setTimeout(fn, 20); }; | ||
var raf = function(fn) { return window.setTimeout(fn, 0); }; | ||
var raf = function(fn) { return setTimeout(fn, 0); }; | ||
return raf(callback); | ||
@@ -117,2 +102,51 @@ } | ||
}; | ||
function Batch() { | ||
var batch = {}; | ||
var size = 0; | ||
var topLevel = 0; | ||
var bottomLevel = 0; | ||
function add(level, fn) { | ||
if(!fn) { | ||
fn = level; | ||
level = 0; | ||
} | ||
if(level > topLevel) { | ||
topLevel = level; | ||
} else if(level < bottomLevel) { | ||
bottomLevel = level; | ||
} | ||
if(!batch[level]) { | ||
batch[level] = []; | ||
} | ||
batch[level].push(fn); | ||
size++; | ||
} | ||
function process() { | ||
for(var level = bottomLevel; level <= topLevel; level++) { | ||
var fns = batch[level]; | ||
for(var i = 0; i < fns.length; i++) { | ||
var fn = fns[i]; | ||
fn(); | ||
} | ||
} | ||
} | ||
function getSize() { | ||
return size; | ||
} | ||
return { | ||
add: add, | ||
process: process, | ||
size: getSize | ||
}; | ||
} | ||
},{"./utils":2}],2:[function(require,module,exports){ | ||
@@ -272,6 +306,8 @@ "use strict"; | ||
var style = getComputedStyle(element); | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
getState(element).startSizeStyle = { | ||
width: style.width, | ||
height: style.height | ||
getState(element).startSize = { | ||
width: width, | ||
height: height | ||
}; | ||
@@ -428,6 +464,7 @@ | ||
// The injected container needs to have a class, so that it may be styled with CSS (pseudo elements). | ||
var detectionContainerClass = "erd_scroll_detection_container"; | ||
if (!batchProcessor) { | ||
throw new Error("Missing required dependency: batchProcessor"); | ||
} | ||
if(!reporter) { | ||
if (!reporter) { | ||
throw new Error("Missing required dependency: reporter."); | ||
@@ -440,5 +477,61 @@ } | ||
// Inject the scrollbar styling that prevents them from appearing sometimes in Chrome. | ||
// The injected container needs to have a class, so that it may be styled with CSS (pseudo elements). | ||
var styleId = "erd_scroll_detection_scrollbar_style"; | ||
var detectionContainerClass = "erd_scroll_detection_container"; | ||
injectScrollStyle(styleId, detectionContainerClass); | ||
function getScrollbarSizes() { | ||
var width = 500; | ||
var height = 500; | ||
var child = document.createElement("div"); | ||
child.style.cssText = "position: absolute; width: " + width*2 + "px; height: " + height*2 + "px; visibility: hidden;"; | ||
var container = document.createElement("div"); | ||
container.style.cssText = "position: absolute; width: " + width + "px; height: " + height + "px; overflow: scroll; visibility: none; top: " + -width*3 + "px; left: " + -height*3 + "px; visibility: hidden;"; | ||
container.appendChild(child); | ||
document.body.insertBefore(container, document.body.firstChild); | ||
var widthSize = width - container.clientWidth; | ||
var heightSize = height - container.clientHeight; | ||
document.body.removeChild(container); | ||
return { | ||
width: widthSize, | ||
height: heightSize | ||
}; | ||
} | ||
function injectScrollStyle(styleId, containerClass) { | ||
function injectStyle(style, method) { | ||
method = method || function (element) { | ||
document.head.appendChild(element); | ||
}; | ||
var styleElement = document.createElement("style"); | ||
styleElement.innerHTML = style; | ||
styleElement.id = styleId; | ||
method(styleElement); | ||
return styleElement; | ||
} | ||
if (!document.getElementById(styleId)) { | ||
var containerAnimationClass = containerClass + "_animation"; | ||
var containerAnimationActiveClass = containerClass + "_animation_active"; | ||
var style = "/* Created by the element-resize-detector library. */\n"; | ||
style += "." + containerClass + " > div::-webkit-scrollbar { display: none; }\n\n"; | ||
style += "." + containerAnimationActiveClass + " { -webkit-animation-duration: 0.1s; animation-duration: 0.1s; -webkit-animation-name: " + containerAnimationClass + "; animation-name: " + containerAnimationClass + "; }\n"; | ||
style += "@-webkit-keyframes " + containerAnimationClass + " { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }\n"; | ||
style += "@keyframes " + containerAnimationClass + " { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }"; | ||
injectStyle(style); | ||
} | ||
} | ||
function addAnimationClass(element) { | ||
element.className += " " + detectionContainerClass + "_animation_active"; | ||
} | ||
/** | ||
@@ -480,365 +573,408 @@ * Adds a resize event listener to the element. | ||
args.unshift(idHandler.get(element), "Scroll: "); | ||
reporter.log.apply(null, args); | ||
if (reporter.log.apply) { | ||
reporter.log.apply(null, args); | ||
} else { | ||
for (var i = 0; i < args.length; i++) { | ||
reporter.log(args[i]); | ||
} | ||
} | ||
} | ||
} | ||
function isStyleResolved() { | ||
function isPxValue(length) { | ||
return length.indexOf("px") !== -1; | ||
function isDetached(element) { | ||
function isInDocument(element) { | ||
return element === element.ownerDocument.body || element.ownerDocument.body.contains(element); | ||
} | ||
return !isInDocument(element); | ||
} | ||
var style = getComputedStyle(element); | ||
function isUnrendered(element) { | ||
// Check the absolute positioned container since the top level container is display: inline. | ||
var container = getState(element).container.childNodes[0]; | ||
return getComputedStyle(container).width.indexOf("px") === -1; //Can only compute pixel value when rendered. | ||
} | ||
return style.position && isPxValue(style.width) && isPxValue(style.height); | ||
function getStyle() { | ||
// Some browsers only force layouts when actually reading the style properties of the style object, so make sure that they are all read here, | ||
// so that the user of the function can be sure that it will perform the layout here, instead of later (important for batching). | ||
var elementStyle = getComputedStyle(element); | ||
var style = {}; | ||
style.position = elementStyle.position; | ||
style.width = element.offsetWidth; | ||
style.height = element.offsetHeight; | ||
style.top = elementStyle.top; | ||
style.right = elementStyle.right; | ||
style.bottom = elementStyle.bottom; | ||
style.left = elementStyle.left; | ||
style.widthCSS = elementStyle.width; | ||
style.heightCSS = elementStyle.height; | ||
return style; | ||
} | ||
function install() { | ||
function getStyle() { | ||
// Some browsers only force layouts when actually reading the style properties of the style object, so make sure that they are all read here, | ||
// so that the user of the function can be sure that it will perform the layout here, instead of later (important for batching). | ||
var style = {}; | ||
var elementStyle = getComputedStyle(element); | ||
style.position = elementStyle.position; | ||
style.width = parseSize(elementStyle.width); | ||
style.height = parseSize(elementStyle.height); | ||
style.top = elementStyle.top; | ||
style.right = elementStyle.right; | ||
style.bottom = elementStyle.bottom; | ||
style.left = elementStyle.left; | ||
style.widthStyle = elementStyle.width; | ||
style.heightStyle = elementStyle.height; | ||
return style; | ||
} | ||
function storeStartSize() { | ||
var style = getStyle(); | ||
getState(element).startSize = { | ||
width: style.width, | ||
height: style.height | ||
}; | ||
debug("Element start size", getState(element).startSize); | ||
} | ||
function storeStartSize() { | ||
var style = getStyle(); | ||
getState(element).startSizeStyle = { | ||
width: style.widthStyle, | ||
height: style.heightStyle | ||
}; | ||
} | ||
function initListeners() { | ||
getState(element).listeners = []; | ||
} | ||
function initListeners() { | ||
getState(element).listeners = []; | ||
} | ||
function storeStyle() { | ||
debug("storeStyle invoked."); | ||
var style = getStyle(); | ||
getState(element).style = style; | ||
} | ||
debug("Installing scroll elements..."); | ||
function storeCurrentSize(element, width, height) { | ||
getState(element).lastWidth = width; | ||
getState(element).lastHeight = height; | ||
} | ||
storeStartSize(); | ||
initListeners(); | ||
function getExpandElement(element) { | ||
return getState(element).container.childNodes[0].childNodes[0].childNodes[0]; | ||
} | ||
debug("Element start size", getState(element).startSizeStyle); | ||
function getExpandChildElement(element) { | ||
return getExpandElement(element).childNodes[0]; | ||
} | ||
function storeStyle() { | ||
debug("storeStyle invoked."); | ||
function getShrinkElement(element) { | ||
return getState(element).container.childNodes[0].childNodes[0].childNodes[1]; | ||
} | ||
// Style is to be retrieved in the first level (before mutating the DOM) so that a forced layout is avoided later. | ||
var style = getStyle(); | ||
getState(element).style = style; | ||
function getWidthOffset() { | ||
return 2 * scrollbarSizes.width + 1; | ||
} | ||
function getHeightOffset() { | ||
return 2 * scrollbarSizes.height + 1; | ||
} | ||
function getExpandWidth(width) { | ||
return width + 10 + getWidthOffset(); | ||
} | ||
function getExpandHeight(height) { | ||
return height + 10 + getHeightOffset(); | ||
} | ||
function getShrinkWidth(width) { | ||
return width * 2 + getWidthOffset(); | ||
} | ||
function getShrinkHeight(height) { | ||
return height * 2 + getHeightOffset(); | ||
} | ||
function positionScrollbars(element, width, height) { | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
var shrinkWidth = getShrinkWidth(width); | ||
var shrinkHeight = getShrinkHeight(height); | ||
expand.scrollLeft = expandWidth; | ||
expand.scrollTop = expandHeight; | ||
shrink.scrollLeft = shrinkWidth; | ||
shrink.scrollTop = shrinkHeight; | ||
} | ||
function addEvent(el, name, cb) { | ||
if (el.addEventListener) { | ||
el.addEventListener(name, cb); | ||
} else if(el.attachEvent) { | ||
el.attachEvent("on" + name, cb); | ||
} else { | ||
return reporter.error("[scroll] Don't know how to add event listeners."); | ||
} | ||
} | ||
function mutateDom() { | ||
debug("mutateDom invoked."); | ||
function injectContainerElement() { | ||
var container = getState(element).container; | ||
if (!container) { | ||
container = document.createElement("div"); | ||
container.className = detectionContainerClass; | ||
container.style.cssText = "visibility: hidden; display: inline; width: 0px; height: 0px; z-index: -1; overflow: hidden;"; | ||
getState(element).container = container; | ||
addAnimationClass(container); | ||
element.appendChild(container); | ||
addEvent(container, "animationstart", function onAnimationStart () { | ||
getState(element).onRendered && getState(element).onRendered(); | ||
}); | ||
} | ||
return container; | ||
} | ||
function injectScrollElements() { | ||
function alterPositionStyles() { | ||
var style = getState(element).style; | ||
function alterPositionStyles() { | ||
if(style.position === "static") { | ||
element.style.position = "relative"; | ||
if(style.position === "static") { | ||
element.style.position = "relative"; | ||
var removeRelativeStyles = function(reporter, element, style, property) { | ||
function getNumericalValue(value) { | ||
return value.replace(/[^-\d\.]/g, ""); | ||
} | ||
var removeRelativeStyles = function(reporter, element, style, property) { | ||
function getNumericalValue(value) { | ||
return value.replace(/[^-\d\.]/g, ""); | ||
} | ||
var value = style[property]; | ||
var value = style[property]; | ||
if(value !== "auto" && getNumericalValue(value) !== "0") { | ||
reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element); | ||
element.style[property] = 0; | ||
} | ||
}; | ||
if(value !== "auto" && getNumericalValue(value) !== "0") { | ||
reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element); | ||
element.style[property] = 0; | ||
} | ||
}; | ||
//Check so that there are no accidental styles that will make the element styled differently now that is is relative. | ||
//If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway). | ||
removeRelativeStyles(reporter, element, style, "top"); | ||
removeRelativeStyles(reporter, element, style, "right"); | ||
removeRelativeStyles(reporter, element, style, "bottom"); | ||
removeRelativeStyles(reporter, element, style, "left"); | ||
} | ||
//Check so that there are no accidental styles that will make the element styled differently now that is is relative. | ||
//If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway). | ||
removeRelativeStyles(reporter, element, style, "top"); | ||
removeRelativeStyles(reporter, element, style, "right"); | ||
removeRelativeStyles(reporter, element, style, "bottom"); | ||
removeRelativeStyles(reporter, element, style, "left"); | ||
} | ||
} | ||
function getContainerCssText(left, top, bottom, right) { | ||
left = (!left ? "0" : (left + "px")); | ||
top = (!top ? "0" : (top + "px")); | ||
bottom = (!bottom ? "0" : (bottom + "px")); | ||
right = (!right ? "0" : (right + "px")); | ||
function getTopBottomBottomRightCssText(left, top, bottom, right) { | ||
left = (!left ? "0" : (left + "px")); | ||
top = (!top ? "0" : (top + "px")); | ||
bottom = (!bottom ? "0" : (bottom + "px")); | ||
right = (!right ? "0" : (right + "px")); | ||
return "position: absolute; left: " + left + "; top: " + top + "; right: " + right + "; bottom: " + bottom + "; overflow: scroll; z-index: -1; visibility: hidden;"; | ||
} | ||
return "left: " + left + "; top: " + top + "; right: " + right + "; bottom: " + bottom + ";"; | ||
} | ||
alterPositionStyles(style); | ||
debug("Injecting elements"); | ||
var scrollbarWidth = scrollbarSizes.width; | ||
var scrollbarHeight = scrollbarSizes.height; | ||
var containerStyle = getContainerCssText(-(1 + scrollbarWidth), -(1 + scrollbarHeight), -scrollbarHeight, -scrollbarWidth); | ||
var shrinkExpandstyle = getContainerCssText(0, 0, -scrollbarHeight, -scrollbarWidth); | ||
var shrinkExpandChildStyle = "position: absolute; left: 0; top: 0;"; | ||
alterPositionStyles(); | ||
var container = document.createElement("div"); | ||
var expand = document.createElement("div"); | ||
var expandChild = document.createElement("div"); | ||
var shrink = document.createElement("div"); | ||
var shrinkChild = document.createElement("div"); | ||
var rootContainer = getState(element).container; | ||
container.className = detectionContainerClass; | ||
container.style.cssText = containerStyle; | ||
expand.style.cssText = shrinkExpandstyle; | ||
expandChild.style.cssText = shrinkExpandChildStyle; | ||
shrink.style.cssText = shrinkExpandstyle; | ||
shrinkChild.style.cssText = shrinkExpandChildStyle + " width: 200%; height: 200%;"; | ||
if (!rootContainer) { | ||
rootContainer = injectContainerElement(); | ||
} | ||
expand.appendChild(expandChild); | ||
shrink.appendChild(shrinkChild); | ||
container.appendChild(expand); | ||
container.appendChild(shrink); | ||
element.appendChild(container); | ||
getState(element).element = container; | ||
// Due to this WebKit bug https://bugs.webkit.org/show_bug.cgi?id=80808 (currently fixed in Blink, but still present in WebKit browsers such as Safari), | ||
// we need to inject two containers, one that is width/height 100% and another that is left/top -1px so that the final container always is 1x1 pixels bigger than | ||
// the targeted element. | ||
// When the bug is resolved, "containerContainer" may be removed. | ||
function handleScroll() { | ||
function changed() { | ||
var elementStyle = getComputedStyle(element); | ||
var width = parseSize(elementStyle.width); | ||
var height = parseSize(elementStyle.height); | ||
// The outer container can occasionally be less wide than the targeted when inside inline elements element in WebKit (see https://bugs.webkit.org/show_bug.cgi?id=152980). | ||
// This should be no problem since the inner container either way makes sure the injected scroll elements are at least 1x1 px. | ||
debug("Storing current size", width, height); | ||
var scrollbarWidth = scrollbarSizes.width; | ||
var scrollbarHeight = scrollbarSizes.height; | ||
var containerContainerStyle = "position: absolute; overflow: hidden; z-index: -1; visibility: hidden; width: 100%; height: 100%; left: 0px; top: 0px;"; | ||
var containerStyle = "position: absolute; overflow: hidden; z-index: -1; visibility: hidden; " + getTopBottomBottomRightCssText(-(1 + scrollbarWidth), -(1 + scrollbarHeight), -scrollbarHeight, -scrollbarWidth); | ||
var expandStyle = "position: absolute; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;"; | ||
var shrinkStyle = "position: absolute; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;"; | ||
var expandChildStyle = "position: absolute; left: 0; top: 0;"; | ||
var shrinkChildStyle = "position: absolute; width: 200%; height: 200%;"; | ||
// Store the size of the element sync here, so that multiple scroll events may be ignored in the event listeners. | ||
// Otherwise the if-check in handleScroll is useless. | ||
storeCurrentSize(element, width, height); | ||
var containerContainer = document.createElement("div"); | ||
var container = document.createElement("div"); | ||
var expand = document.createElement("div"); | ||
var expandChild = document.createElement("div"); | ||
var shrink = document.createElement("div"); | ||
var shrinkChild = document.createElement("div"); | ||
batchProcessor.add(function updateDetectorElements() { | ||
if (options.debug) { | ||
var style = getComputedStyle(element); | ||
var w = parseSize(style.width); | ||
var h = parseSize(style.height); | ||
containerContainer.style.cssText = containerContainerStyle; | ||
containerContainer.className = detectionContainerClass; | ||
container.className = detectionContainerClass; | ||
container.style.cssText = containerStyle; | ||
expand.style.cssText = expandStyle; | ||
expandChild.style.cssText = expandChildStyle; | ||
shrink.style.cssText = shrinkStyle; | ||
shrinkChild.style.cssText = shrinkChildStyle; | ||
if (w !== width || h !== height) { | ||
reporter.warn(idHandler.get(element), "Scroll: Size changed before updating detector elements."); | ||
} | ||
} | ||
expand.appendChild(expandChild); | ||
shrink.appendChild(shrinkChild); | ||
container.appendChild(expand); | ||
container.appendChild(shrink); | ||
containerContainer.appendChild(container); | ||
rootContainer.appendChild(containerContainer); | ||
updateChildSizes(element, width, height); | ||
}); | ||
addEvent(expand, "scroll", function onExpandScroll() { | ||
getState(element).onExpand && getState(element).onExpand(); | ||
}); | ||
batchProcessor.add(1, function updateScrollbars() { | ||
positionScrollbars(element, width, height); | ||
forEach(getState(element).listeners, function (listener) { | ||
listener(element); | ||
}); | ||
}); | ||
} | ||
addEvent(shrink, "scroll", function onShrinkScroll() { | ||
getState(element).onShrink && getState(element).onShrink(); | ||
}); | ||
} | ||
debug("Scroll detected."); | ||
function registerListenersAndPositionElements() { | ||
debug("registerListenersAndPositionElements invoked."); | ||
var style = getComputedStyle(element); | ||
var width = parseSize(style.width); | ||
var height = parseSize(style.height); | ||
function updateChildSizes(element, width, height) { | ||
var expandChild = getExpandChildElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
expandChild.style.width = expandWidth + "px"; | ||
expandChild.style.height = expandHeight + "px"; | ||
} | ||
if (width !== element.lastWidth || height !== element.lastHeight) { | ||
debug("Element size changed."); | ||
changed(); | ||
function updateDetectorElements(done) { | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
debug("Storing current size", width, height); | ||
// Store the size of the element sync here, so that multiple scroll events may be ignored in the event listeners. | ||
// Otherwise the if-check in handleScroll is useless. | ||
storeCurrentSize(element, width, height); | ||
batchProcessor.add(0, function performUpdateChildSizes() { | ||
if (options.debug) { | ||
var w = element.offsetWidth; | ||
var h = element.offsetHeight; | ||
if (w !== width || h !== height) { | ||
reporter.warn(idHandler.get(element), "Scroll: Size changed before updating detector elements."); | ||
} | ||
} | ||
} | ||
addEvent(expand, "scroll", function onExpand() { | ||
handleScroll(); | ||
updateChildSizes(element, width, height); | ||
}); | ||
addEvent(shrink, "scroll", function onShrink() { | ||
handleScroll(); | ||
batchProcessor.add(1, function updateScrollbars() { | ||
positionScrollbars(element, width, height); | ||
}); | ||
updateChildSizes(element, style.width, style.height); | ||
if (done) { | ||
batchProcessor.add(2, done); | ||
} | ||
} | ||
function finalizeDomMutation() { | ||
debug("finalizeDomMutation invoked."); | ||
var style = getState(element).style; | ||
storeCurrentSize(element, style.width, style.height); | ||
positionScrollbars(element, style.width, style.height); | ||
function areElementsInjected() { | ||
return !!getState(element).container; | ||
} | ||
function ready() { | ||
callback(element); | ||
} | ||
function notifyListenersIfNeeded() { | ||
function isFirstNotify() { | ||
return getState(element).lastNotifiedWidth === undefined; | ||
} | ||
if(batchProcessor) { | ||
batchProcessor.add(0, storeStyle); | ||
batchProcessor.add(1, mutateDom); | ||
batchProcessor.add(2, finalizeDomMutation); | ||
batchProcessor.add(3, ready); | ||
} else { | ||
storeStyle(); | ||
mutateDom(); | ||
finalizeDomMutation(); | ||
ready(); | ||
} | ||
} | ||
debug("notifyListenersIfNeeded invoked"); | ||
debug("Making detectable..."); | ||
var state = getState(element); | ||
// Only install the strategy if the style has been resolved (this does not always mean that the element is attached). | ||
if (isStyleResolved()) { | ||
debug("Style resolved"); | ||
install(); | ||
} else { | ||
debug("Style not resolved"); | ||
debug("Polling for style resolution..."); | ||
// Don't notify the if the current size is the start size, and this is the first notification. | ||
if (isFirstNotify() && state.lastWidth === state.startSize.width && state.lastHeight === state.startSize.height) { | ||
return debug("Not notifying: Size is the same as the start size, and there has been no notification yet."); | ||
} | ||
// Need to perform polling in order to detect when the element has been attached to the DOM. | ||
var timeout = setInterval(function () { | ||
if (isStyleResolved()) { | ||
debug("Poll. Style resolved."); | ||
install(); | ||
clearTimeout(timeout); | ||
} else { | ||
debug("Poll. Style not resolved."); | ||
// Don't notify if the size already has been notified. | ||
if (state.lastWidth === state.lastNotifiedWidth && state.lastHeight === state.lastNotifiedHeight) { | ||
return debug("Not notifying: Size already notified"); | ||
} | ||
}, 50); | ||
} | ||
} | ||
function getExpandElement(element) { | ||
return getState(element).element.childNodes[0]; | ||
} | ||
function getExpandChildElement(element) { | ||
return getExpandElement(element).childNodes[0]; | ||
} | ||
debug("Current size not notified, notifying..."); | ||
state.lastNotifiedWidth = state.lastWidth; | ||
state.lastNotifiedHeight = state.lastHeight; | ||
forEach(getState(element).listeners, function (listener) { | ||
listener(element); | ||
}); | ||
} | ||
function getShrinkElement(element) { | ||
return getState(element).element.childNodes[1]; | ||
} | ||
function handleRender() { | ||
debug("startanimation triggered."); | ||
function getWidthOffset() { | ||
return 2 * scrollbarSizes.width + 1; | ||
} | ||
if (isUnrendered(element)) { | ||
debug("Ignoring since element is still unrendered..."); | ||
return; | ||
} | ||
function getHeightOffset() { | ||
return 2 * scrollbarSizes.height + 1; | ||
} | ||
debug("Element rendered."); | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
if (expand.scrollLeft === 0 || expand.scrollTop === 0 || shrink.scrollLeft === 0 || shrink.scrollTop === 0) { | ||
debug("Scrollbars out of sync. Updating detector elements..."); | ||
updateDetectorElements(notifyListenersIfNeeded); | ||
} | ||
} | ||
function getExpandWidth(width) { | ||
return width + 10 + getWidthOffset(); | ||
} | ||
function handleScroll() { | ||
debug("Scroll detected."); | ||
function getExpandHeight(height) { | ||
return height + 10 + getHeightOffset(); | ||
} | ||
if (isUnrendered(element)) { | ||
// Element is still unrendered. Skip this scroll event. | ||
debug("Scroll event fired while unrendered. Ignoring..."); | ||
return; | ||
} | ||
function getShrinkWidth(width) { | ||
return width * 2 + getWidthOffset(); | ||
} | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
function getShrinkHeight(height) { | ||
return height * 2 + getHeightOffset(); | ||
} | ||
if (width !== element.lastWidth || height !== element.lastHeight) { | ||
debug("Element size changed."); | ||
updateDetectorElements(notifyListenersIfNeeded); | ||
} else { | ||
debug("Element size has not changed (" + width + "x" + height + ")."); | ||
} | ||
} | ||
function updateChildSizes(element, width, height) { | ||
var expandChild = getExpandChildElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
expandChild.style.width = expandWidth + "px"; | ||
expandChild.style.height = expandHeight + "px"; | ||
} | ||
getState(element).onRendered = handleRender; | ||
getState(element).onExpand = handleScroll; | ||
getState(element).onShrink = handleScroll; | ||
function storeCurrentSize(element, width, height) { | ||
element.lastWidth = width; | ||
element.lastHeight = height; | ||
} | ||
var style = getState(element).style; | ||
updateChildSizes(element, style.width, style.height); | ||
} | ||
function positionScrollbars(element, width, height) { | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
var shrinkWidth = getShrinkWidth(width); | ||
var shrinkHeight = getShrinkHeight(height); | ||
expand.scrollLeft = expandWidth; | ||
expand.scrollTop = expandHeight; | ||
shrink.scrollLeft = shrinkWidth; | ||
shrink.scrollTop = shrinkHeight; | ||
} | ||
function finalizeDomMutation() { | ||
debug("finalizeDomMutation invoked."); | ||
function addEvent(el, name, cb) { | ||
if (el.attachEvent) { | ||
el.attachEvent("on" + name, cb); | ||
} else { | ||
el.addEventListener(name, cb); | ||
var style = getState(element).style; | ||
storeCurrentSize(element, style.width, style.height); | ||
positionScrollbars(element, style.width, style.height); | ||
} | ||
} | ||
function removeEvent(el, name, cb) { | ||
if(el.attachEvent) { | ||
el.detachEvent("on" + name, cb); | ||
} else { | ||
el.removeEventListener(name, cb); | ||
function ready() { | ||
callback(element); | ||
} | ||
} | ||
function parseSize(size) { | ||
return parseFloat(size.replace(/px/, "")); | ||
} | ||
function install() { | ||
debug("Installing..."); | ||
initListeners(); | ||
storeStartSize(); | ||
function getScrollbarSizes() { | ||
var width = 500; | ||
var height = 500; | ||
batchProcessor.add(0, storeStyle); | ||
batchProcessor.add(1, injectScrollElements); | ||
batchProcessor.add(2, registerListenersAndPositionElements); | ||
batchProcessor.add(3, finalizeDomMutation); | ||
batchProcessor.add(4, ready); | ||
} | ||
var child = document.createElement("div"); | ||
child.style.cssText = "position: absolute; width: " + width*2 + "px; height: " + height*2 + "px; visibility: hidden;"; | ||
debug("Making detectable..."); | ||
var container = document.createElement("div"); | ||
container.style.cssText = "position: absolute; width: " + width + "px; height: " + height + "px; overflow: scroll; visibility: none; top: " + -width*3 + "px; left: " + -height*3 + "px; visibility: hidden;"; | ||
if (isDetached(element)) { | ||
debug("Element is detached"); | ||
container.appendChild(child); | ||
injectContainerElement(); | ||
document.body.insertBefore(container, document.body.firstChild); | ||
debug("Waiting until element is attached..."); | ||
var widthSize = width - container.clientWidth; | ||
var heightSize = height - container.clientHeight; | ||
document.body.removeChild(container); | ||
return { | ||
width: widthSize, | ||
height: heightSize | ||
}; | ||
} | ||
function injectScrollStyle(styleId, containerClass) { | ||
function injectStyle(style, method) { | ||
method = method || function (element) { | ||
document.head.appendChild(element); | ||
getState(element).onRendered = function () { | ||
debug("Element is now attached"); | ||
install(); | ||
}; | ||
var styleElement = document.createElement("style"); | ||
styleElement.innerHTML = style; | ||
styleElement.id = styleId; | ||
method(styleElement); | ||
return styleElement; | ||
} else { | ||
install(); | ||
} | ||
if (!document.getElementById(styleId)) { | ||
var style = "/* Created by the element-resize-detector library. */\n"; | ||
style += "." + containerClass + " > div::-webkit-scrollbar { display: none; }"; | ||
injectStyle(style); | ||
} | ||
} | ||
function uninstall(element) { | ||
//TODO: This should also delete the added state object of the element. | ||
var state = getState(element); | ||
element.removeChild(state.element); | ||
delete state.element; | ||
element.removeChild(state.container); | ||
delete state.container; | ||
} | ||
@@ -943,5 +1079,10 @@ | ||
if(desiredStrategy === "scroll" && browserDetector.isLegacyOpera()) { | ||
reporter.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."); | ||
desiredStrategy = "object"; | ||
if(desiredStrategy === "scroll") { | ||
if (browserDetector.isLegacyOpera()) { | ||
reporter.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."); | ||
desiredStrategy = "object"; | ||
} else if (browserDetector.isIE(9)) { | ||
reporter.warn("Scroll strategy is not supported on IE9. Changing to object strategy."); | ||
desiredStrategy = "object"; | ||
} | ||
} | ||
@@ -1027,2 +1168,3 @@ | ||
// Convert collection to array for plugins. | ||
// TODO: May want to check so that all the elements in the collection are valid elements. | ||
elements = toArray(elements); | ||
@@ -1076,5 +1218,9 @@ } else { | ||
// so that a resize event may be emitted. | ||
var style = getComputedStyle(element); | ||
if (stateHandler.getState(element).startSizeStyle.width !== style.width || stateHandler.getState(element).startSizeStyle.height !== style.height) { | ||
onResizeCallback(element); | ||
// Having the startSize object is optional (since it does not make sense in some cases such as unrendered elements), so check for its existance before. | ||
if (stateHandler.getState(element).startSize) { | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
if (stateHandler.getState(element).startSize.width !== width || stateHandler.getState(element).startSize.height !== height) { | ||
onResizeCallback(element); | ||
} | ||
} | ||
@@ -1331,3 +1477,10 @@ | ||
reporter[name] = function reporterProxy() { | ||
console[name].apply(console, arguments); | ||
var f = console[name]; | ||
if (f.apply) { //IE9 does not support console.log.apply :) | ||
f.apply(console, arguments); | ||
} else { | ||
for (var i = 0; i < arguments.length; i++) { | ||
f(arguments[i]); | ||
} | ||
} | ||
}; | ||
@@ -1334,0 +1487,0 @@ }; |
/*! | ||
* element-resize-detector 1.0.3 | ||
* element-resize-detector 1.1.0 | ||
* https://github.com/wnr/element-resize-detector | ||
@@ -7,2 +7,2 @@ * Licensed under MIT | ||
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.elementResizeDetectorMaker=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";var d=a("./utils");b.exports=function(a){function b(a,b){b||(b=a,a=0),a>o?o=a:p>a&&(p=a),m[a]||(m[a]=[]),l&&k&&0===n&&f(),m[a].push(b),n++}function c(a){void 0===a&&(a=k),q&&(h(q),q=null),a?f():e()}function e(){for(var a=p;o>=a;a++)for(var b=m[a],c=0;c<b.length;c++){var d=b[c];d()}g()}function f(){q=i(e)}function g(){m={},n=0,o=0,p=0}function h(a){var b=window.clearTimeout;return b(a)}function i(a){var b=function(a){return window.setTimeout(a,0)};return b(a)}a=a||{};var j=a.reporter,k=d.getOption(a,"async",!0),l=d.getOption(a,"auto",!0);l&&!k&&(j&&j.warn("Invalid options combination. auto=true and async=false is invalid. Setting async=true."),k=!0);var m,n,o,p;g();var q;return{add:b,force:c}}},{"./utils":2}],2:[function(a,b,c){"use strict";function d(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var e=b.exports={};e.getOption=d},{}],3:[function(a,b,c){"use strict";var d=b.exports={};d.isIE=function(a){function b(){var a=navigator.userAgent.toLowerCase();return-1!==a.indexOf("msie")||-1!==a.indexOf("trident")||-1!==a.indexOf(" edge/")}if(!b())return!1;if(!a)return!0;var c=function(){var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");do c.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->";while(d[0]);return b>4?b:a}();return a===c},d.isLegacyOpera=function(){return!!window.opera}},{}],4:[function(a,b,c){"use strict";var d=b.exports={};d.forEach=function(a,b){for(var c=0;c<a.length;c++){var d=b(a[c]);if(d)return d}}},{}],5:[function(a,b,c){"use strict";var d=a("../browser-detector");b.exports=function(a){function b(a,b){function c(){b(a)}if(!e(a))throw new Error("Element is not detectable by this strategy.");if(d.isIE(8))i(a).object={proxy:c},a.attachEvent("onresize",c);else{var f=e(a);f.contentDocument.defaultView.addEventListener("resize",c)}}function c(a,b,c){function e(a,b){function c(){function c(){if("static"===j.position){a.style.position="relative";var b=function(a,b,c,d){function e(a){return a.replace(/[^-\d\.]/g,"")}var f=c[d];"auto"!==f&&"0"!==e(f)&&(a.warn("An element that is positioned static has style."+d+"="+f+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};b(g,a,j,"top"),b(g,a,j,"right"),b(g,a,j,"bottom"),b(g,a,j,"left")}}function h(){function d(a,b){return a.contentDocument?void b(a.contentDocument):void setTimeout(function(){d(a,b)},100)}f||c();var e=this;d(e,function(c){b(a)})}""!==j.position&&(c(j),f=!0);var k=document.createElement("object");k.style.cssText=e,k.type="text/html",k.onload=h,d.isIE()||(k.data="about:blank"),a.appendChild(k),i(a).object=k,d.isIE()&&(k.data="about:blank")}var e="display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;",f=!1,j=getComputedStyle(a);i(a).startSizeStyle={width:j.width,height:j.height},h?h.add(c):c()}c||(c=b,b=a,a=null),a=a||{};a.debug;d.isIE(8)?c(b):e(b,c)}function e(a){return i(a).object}function f(a){d.isIE(8)?a.detachEvent("onresize",i(a).object.proxy):a.removeChild(e(a)),delete i(a).object}a=a||{};var g=a.reporter,h=a.batchProcessor,i=a.stateHandler.getState;if(!g)throw new Error("Missing required dependency: reporter.");return{makeDetectable:c,addListener:b,uninstall:f}}},{"../browser-detector":3}],6:[function(a,b,c){"use strict";var d=a("../collection-utils").forEach;b.exports=function(a){function b(a,b){var c=x(a).listeners;if(!c.push)throw new Error("Cannot add listener to an element that is not detectable.");x(a).listeners.push(b)}function c(a,b,c){function e(){if(a.debug){var c=Array.prototype.slice.call(arguments);c.unshift(y.get(b),"Scroll: "),v.log.apply(null,c)}}function f(){function a(a){return-1!==a.indexOf("px")}var c=getComputedStyle(b);return c.position&&a(c.width)&&a(c.height)}function g(){function f(){var a={},c=getComputedStyle(b);return a.position=c.position,a.width=r(c.width),a.height=r(c.height),a.top=c.top,a.right=c.right,a.bottom=c.bottom,a.left=c.left,a.widthStyle=c.width,a.heightStyle=c.height,a}function g(){var a=f();x(b).startSizeStyle={width:a.widthStyle,height:a.heightStyle}}function h(){x(b).listeners=[]}function i(){e("storeStyle invoked.");var a=f();x(b).style=a}function j(){function c(){if("static"===h.position){b.style.position="relative";var a=function(a,b,c,d){function e(a){return a.replace(/[^-\d\.]/g,"")}var f=c[d];"auto"!==f&&"0"!==e(f)&&(a.warn("An element that is positioned static has style."+d+"="+f+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};a(v,b,h,"top"),a(v,b,h,"right"),a(v,b,h,"bottom"),a(v,b,h,"left")}}function f(a,b,c,d){return a=a?a+"px":"0",b=b?b+"px":"0",c=c?c+"px":"0",d=d?d+"px":"0","position: absolute; left: "+a+"; top: "+b+"; right: "+d+"; bottom: "+c+"; overflow: scroll; z-index: -1; visibility: hidden;"}function g(){function c(){var c=getComputedStyle(b),f=r(c.width),g=r(c.height);e("Storing current size",f,g),o(b,f,g),w.add(function(){if(a.debug){var c=getComputedStyle(b),d=r(c.width),e=r(c.height);(d!==f||e!==g)&&v.warn(y.get(b),"Scroll: Size changed before updating detector elements.")}n(b,f,g)}),w.add(1,function(){p(b,f,g),d(x(b).listeners,function(a){a(b)})})}e("Scroll detected.");var f=getComputedStyle(b),g=r(f.width),h=r(f.height);(g!==b.lastWidth||h!==b.lastHeight)&&(e("Element size changed."),c())}e("mutateDom invoked.");var h=x(b).style;c(h);var i=A.width,j=A.height,k=f(-(1+i),-(1+j),-j,-i),l=f(0,0,-j,-i),m="position: absolute; left: 0; top: 0;",s=document.createElement("div"),t=document.createElement("div"),u=document.createElement("div"),B=document.createElement("div"),C=document.createElement("div");s.className=z,s.style.cssText=k,t.style.cssText=l,u.style.cssText=m,B.style.cssText=l,C.style.cssText=m+" width: 200%; height: 200%;",t.appendChild(u),B.appendChild(C),s.appendChild(t),s.appendChild(B),b.appendChild(s),x(b).element=s,q(t,"scroll",function(){g()}),q(B,"scroll",function(){g()}),n(b,h.width,h.height)}function k(){e("finalizeDomMutation invoked.");var a=x(b).style;o(b,a.width,a.height),p(b,a.width,a.height)}function l(){c(b)}e("Installing scroll elements..."),g(),h(),e("Element start size",x(b).startSizeStyle),w?(w.add(0,i),w.add(1,j),w.add(2,k),w.add(3,l)):(i(),j(),k(),l())}if(c||(c=b,b=a,a=null),a=a||{},e("Making detectable..."),f())e("Style resolved"),g();else{e("Style not resolved"),e("Polling for style resolution...");var h=setInterval(function(){f()?(e("Poll. Style resolved."),g(),clearTimeout(h)):e("Poll. Style not resolved.")},50)}}function e(a){return x(a).element.childNodes[0]}function f(a){return e(a).childNodes[0]}function g(a){return x(a).element.childNodes[1]}function h(){return 2*A.width+1}function i(){return 2*A.height+1}function j(a){return a+10+h()}function k(a){return a+10+i()}function l(a){return 2*a+h()}function m(a){return 2*a+i()}function n(a,b,c){var d=f(a),e=j(b),g=k(c);d.style.width=e+"px",d.style.height=g+"px"}function o(a,b,c){a.lastWidth=b,a.lastHeight=c}function p(a,b,c){var d=e(a),f=g(a),h=j(b),i=k(c),n=l(b),o=m(c);d.scrollLeft=h,d.scrollTop=i,f.scrollLeft=n,f.scrollTop=o}function q(a,b,c){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener(b,c)}function r(a){return parseFloat(a.replace(/px/,""))}function s(){var a=500,b=500,c=document.createElement("div");c.style.cssText="position: absolute; width: "+2*a+"px; height: "+2*b+"px; visibility: hidden;";var d=document.createElement("div");d.style.cssText="position: absolute; width: "+a+"px; height: "+b+"px; overflow: scroll; visibility: none; top: "+3*-a+"px; left: "+3*-b+"px; visibility: hidden;",d.appendChild(c),document.body.insertBefore(d,document.body.firstChild);var e=a-d.clientWidth,f=b-d.clientHeight;return document.body.removeChild(d),{width:e,height:f}}function t(a,b){function c(b,c){c=c||function(a){document.head.appendChild(a)};var d=document.createElement("style");return d.innerHTML=b,d.id=a,c(d),d}if(!document.getElementById(a)){var d="/* Created by the element-resize-detector library. */\n";d+="."+b+" > div::-webkit-scrollbar { display: none; }",c(d)}}function u(a){var b=x(a);a.removeChild(b.element),delete b.element}a=a||{};var v=a.reporter,w=a.batchProcessor,x=a.stateHandler.getState,y=a.idHandler,z="erd_scroll_detection_container";if(!v)throw new Error("Missing required dependency: reporter.");var A=s(),B="erd_scroll_detection_scrollbar_style";return t(B,z),{makeDetectable:c,addListener:b,uninstall:u}}},{"../collection-utils":4}],7:[function(a,b,c){"use strict";function d(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var e=a("./collection-utils").forEach,f=a("./element-utils"),g=a("./listener-handler"),h=a("./id-generator"),i=a("./id-handler"),j=a("./reporter"),k=a("./browser-detector"),l=a("batch-processor"),m=a("./state-handler"),n=a("./detection-strategy/object.js"),o=a("./detection-strategy/scroll.js");b.exports=function(a){function b(a,b,c){function f(a){var b=x.get(a);e(b,function(b){b(a)})}function g(a,b,c){x.add(b,c),a&&c(b)}function h(a){return Array.isArray(a)||void 0!==a.length}function i(a){if(Array.isArray(a))return a;var c=[];return e(b,function(a){c.push(a)}),c}function j(a){return a&&1===a.nodeType}if(c||(c=b,b=a,a={}),!b)throw new Error("At least one element required.");if(!c)throw new Error("Listener required.");if(j(b))b=[b];else{if(!h(b))return s.error("Invalid arguments. Must be a DOM element or a collection of DOM elements.");b=i(b)}var k=0,l=d(a,"callOnAdd",v.callOnAdd),n=d(a,"onReady",function(){}),o=d(a,"debug",v.debug);e(b,function(a){var d=p.get(a);return o&&s.log("Attaching listener to element",d,a),y.isDetectable(a)?(o&&s.log(d,"Already detecable, adding listener."),g(l,a,c),void k++):(o&&s.log(d,"Not detectable."),y.isBusy(a)?(o&&s.log(d,"System busy making it detectable"),g(l,a,c),B[d]=B[d]||[],void B[d].push(function(){k++,k===b.length&&n()})):(o&&s.log(d,"Making detectable..."),y.markBusy(a,!0),w.makeDetectable({debug:o},a,function(a){o&&s.log(d,"onElementDetectable"),y.markAsDetectable(a),y.markBusy(a,!1),w.addListener(a,f),g(l,a,c);var h=getComputedStyle(a);(m.getState(a).startSizeStyle.width!==h.width||m.getState(a).startSizeStyle.height!==h.height)&&f(a),k++,k===b.length&&n(),B[d]&&(e(B[d],function(a){a()}),delete B[d])})))}),k===b.length&&n()}function c(a){x.removeAllListeners(a),w.uninstall(a),m.cleanState(a)}a=a||{};var p=a.idHandler;if(!p){var q=h(),r=i({idGenerator:q,stateHandler:m});p=r}var s=a.reporter;if(!s){var t=s===!1;s=j(t)}var u=d(a,"batchProcessor",l({reporter:s})),v={};v.callOnAdd=!!d(a,"callOnAdd",!0),v.debug=!!d(a,"debug",!1);var w,x=g(p),y=f({stateHandler:m}),z=d(a,"strategy","object"),A={reporter:s,batchProcessor:u,stateHandler:m,idHandler:p};if("scroll"===z&&k.isLegacyOpera()&&(s.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."),z="object"),"scroll"===z)w=o(A);else{if("object"!==z)throw new Error("Invalid strategy name: "+z);w=n(A)}var B={};return{listenTo:b,removeListener:x.removeListener,removeAllListeners:x.removeAllListeners,uninstall:c}}},{"./browser-detector":3,"./collection-utils":4,"./detection-strategy/object.js":5,"./detection-strategy/scroll.js":6,"./element-utils":8,"./id-generator":9,"./id-handler":10,"./listener-handler":11,"./reporter":12,"./state-handler":13,"batch-processor":1}],8:[function(a,b,c){"use strict";b.exports=function(a){function b(a){return!!f(a).isDetectable}function c(a){f(a).isDetectable=!0}function d(a){return!!f(a).busy}function e(a,b){f(a).busy=!!b}var f=a.stateHandler.getState;return{isDetectable:b,markAsDetectable:c,isBusy:d,markBusy:e}}},{}],9:[function(a,b,c){"use strict";b.exports=function(){function a(){return b++}var b=1;return{generate:a}}},{}],10:[function(a,b,c){"use strict";b.exports=function(a){function b(a,b){return b||d(a)||c(a),g(a).id}function c(a){var b=f.generate();return g(a).id=b,b}function d(a){return void 0!==g(a).id}function e(a){delete g(a).id}var f=a.idGenerator,g=a.stateHandler.getState;return{get:b,remove:e}}},{}],11:[function(a,b,c){"use strict";b.exports=function(a){function b(b){return f[a.get(b)]||[]}function c(b,c){var d=a.get(b);f[d]||(f[d]=[]),f[d].push(c)}function d(a,c){for(var d=b(a),e=0,f=d.length;f>e;++e)if(d[e]===c){d.splice(e,1);break}}function e(b){var c=f[a.get(b)];c&&(c.length=0)}var f={};return{get:b,add:c,removeListener:d,removeAllListeners:e}}},{}],12:[function(a,b,c){"use strict";b.exports=function(a){function b(){}var c={log:b,warn:b,error:b};if(!a&&window.console){var d=function(a,b){a[b]=function(){console[b].apply(console,arguments)}};d(c,"log"),d(c,"warn"),d(c,"error")}return c}},{}],13:[function(a,b,c){"use strict";function d(a){return a[g]={},e(a)}function e(a){return a[g]||d(a)}function f(a){delete a[g]}var g="_erd";b.exports={initState:d,getState:e,cleanState:f}},{}]},{},[7])(7)}); | ||
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.elementResizeDetectorMaker=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";function d(){function a(a,b){b||(b=a,a=0),a>f?f=a:g>a&&(g=a),d[a]||(d[a]=[]),d[a].push(b),e++}function b(){for(var a=g;f>=a;a++)for(var b=d[a],c=0;c<b.length;c++){var e=b[c];e()}}function c(){return e}var d={},e=0,f=0,g=0;return{add:a,process:b,size:c}}var e=a("./utils");b.exports=function(a){function b(a,b){!o&&l&&k&&0===n.size()&&g(),n.add(a,b)}function c(){for(o=!0;n.size();){var a=n;n=d(),a.process()}o=!1}function f(a){o||(void 0===a&&(a=k),m&&(h(m),m=null),a?g():c())}function g(){m=i(c)}function h(a){var b=clearTimeout;return b(a)}function i(a){var b=function(a){return setTimeout(a,0)};return b(a)}a=a||{};var j=a.reporter,k=e.getOption(a,"async",!0),l=e.getOption(a,"auto",!0);l&&!k&&(j&&j.warn("Invalid options combination. auto=true and async=false is invalid. Setting async=true."),k=!0);var m,n=d(),o=!1;return{add:b,force:f}}},{"./utils":2}],2:[function(a,b,c){"use strict";function d(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var e=b.exports={};e.getOption=d},{}],3:[function(a,b,c){"use strict";var d=b.exports={};d.isIE=function(a){function b(){var a=navigator.userAgent.toLowerCase();return-1!==a.indexOf("msie")||-1!==a.indexOf("trident")||-1!==a.indexOf(" edge/")}if(!b())return!1;if(!a)return!0;var c=function(){var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");do c.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->";while(d[0]);return b>4?b:a}();return a===c},d.isLegacyOpera=function(){return!!window.opera}},{}],4:[function(a,b,c){"use strict";var d=b.exports={};d.forEach=function(a,b){for(var c=0;c<a.length;c++){var d=b(a[c]);if(d)return d}}},{}],5:[function(a,b,c){"use strict";var d=a("../browser-detector");b.exports=function(a){function b(a,b){function c(){b(a)}if(!e(a))throw new Error("Element is not detectable by this strategy.");if(d.isIE(8))i(a).object={proxy:c},a.attachEvent("onresize",c);else{var f=e(a);f.contentDocument.defaultView.addEventListener("resize",c)}}function c(a,b,c){function e(a,b){function c(){function c(){if("static"===j.position){a.style.position="relative";var b=function(a,b,c,d){function e(a){return a.replace(/[^-\d\.]/g,"")}var f=c[d];"auto"!==f&&"0"!==e(f)&&(a.warn("An element that is positioned static has style."+d+"="+f+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};b(g,a,j,"top"),b(g,a,j,"right"),b(g,a,j,"bottom"),b(g,a,j,"left")}}function h(){function d(a,b){return a.contentDocument?void b(a.contentDocument):void setTimeout(function(){d(a,b)},100)}f||c();var e=this;d(e,function(c){b(a)})}""!==j.position&&(c(j),f=!0);var k=document.createElement("object");k.style.cssText=e,k.type="text/html",k.onload=h,d.isIE()||(k.data="about:blank"),a.appendChild(k),i(a).object=k,d.isIE()&&(k.data="about:blank")}var e="display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;",f=!1,j=getComputedStyle(a),k=a.offsetWidth,l=a.offsetHeight;i(a).startSize={width:k,height:l},h?h.add(c):c()}c||(c=b,b=a,a=null),a=a||{};a.debug;d.isIE(8)?c(b):e(b,c)}function e(a){return i(a).object}function f(a){d.isIE(8)?a.detachEvent("onresize",i(a).object.proxy):a.removeChild(e(a)),delete i(a).object}a=a||{};var g=a.reporter,h=a.batchProcessor,i=a.stateHandler.getState;if(!g)throw new Error("Missing required dependency: reporter.");return{makeDetectable:c,addListener:b,uninstall:f}}},{"../browser-detector":3}],6:[function(a,b,c){"use strict";var d=a("../collection-utils").forEach;b.exports=function(a){function b(){var a=500,b=500,c=document.createElement("div");c.style.cssText="position: absolute; width: "+2*a+"px; height: "+2*b+"px; visibility: hidden;";var d=document.createElement("div");d.style.cssText="position: absolute; width: "+a+"px; height: "+b+"px; overflow: scroll; visibility: none; top: "+3*-a+"px; left: "+3*-b+"px; visibility: hidden;",d.appendChild(c),document.body.insertBefore(d,document.body.firstChild);var e=a-d.clientWidth,f=b-d.clientHeight;return document.body.removeChild(d),{width:e,height:f}}function c(a,b){function c(b,c){c=c||function(a){document.head.appendChild(a)};var d=document.createElement("style");return d.innerHTML=b,d.id=a,c(d),d}if(!document.getElementById(a)){var d=b+"_animation",e=b+"_animation_active",f="/* Created by the element-resize-detector library. */\n";f+="."+b+" > div::-webkit-scrollbar { display: none; }\n\n",f+="."+e+" { -webkit-animation-duration: 0.1s; animation-duration: 0.1s; -webkit-animation-name: "+d+"; animation-name: "+d+"; }\n",f+="@-webkit-keyframes "+d+" { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }\n",f+="@keyframes "+d+" { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }",c(f)}}function e(a){a.className+=" "+o+"_animation_active"}function f(a,b){var c=k(a).listeners;if(!c.push)throw new Error("Cannot add listener to an element that is not detectable.");k(a).listeners.push(b)}function g(a,b,c){function f(){if(a.debug){var c=Array.prototype.slice.call(arguments);if(c.unshift(l.get(b),"Scroll: "),i.log.apply)i.log.apply(null,c);else for(var d=0;d<c.length;d++)i.log(c[d])}}function g(a){function b(a){return a===a.ownerDocument.body||a.ownerDocument.body.contains(a)}return!b(a)}function h(a){var b=k(a).container.childNodes[0];return-1===getComputedStyle(b).width.indexOf("px")}function n(){var a=getComputedStyle(b),c={};return c.position=a.position,c.width=b.offsetWidth,c.height=b.offsetHeight,c.top=a.top,c.right=a.right,c.bottom=a.bottom,c.left=a.left,c.widthCSS=a.width,c.heightCSS=a.height,c}function p(){var a=n();k(b).startSize={width:a.width,height:a.height},f("Element start size",k(b).startSize)}function q(){k(b).listeners=[]}function r(){f("storeStyle invoked.");var a=n();k(b).style=a}function s(a,b,c){k(a).lastWidth=b,k(a).lastHeight=c}function t(a){return k(a).container.childNodes[0].childNodes[0].childNodes[0]}function u(a){return t(a).childNodes[0]}function v(a){return k(a).container.childNodes[0].childNodes[0].childNodes[1]}function w(){return 2*m.width+1}function x(){return 2*m.height+1}function y(a){return a+10+w()}function z(a){return a+10+x()}function A(a){return 2*a+w()}function B(a){return 2*a+x()}function C(a,b,c){var d=t(a),e=v(a),f=y(b),g=z(c),h=A(b),i=B(c);d.scrollLeft=f,d.scrollTop=g,e.scrollLeft=h,e.scrollTop=i}function D(a,b,c){if(a.addEventListener)a.addEventListener(b,c);else{if(!a.attachEvent)return i.error("[scroll] Don't know how to add event listeners.");a.attachEvent("on"+b,c)}}function E(){var a=k(b).container;return a||(a=document.createElement("div"),a.className=o,a.style.cssText="visibility: hidden; display: inline; width: 0px; height: 0px; z-index: -1; overflow: hidden;",k(b).container=a,e(a),b.appendChild(a),D(a,"animationstart",function(){k(b).onRendered&&k(b).onRendered()})),a}function F(){function a(){var a=k(b).style;if("static"===a.position){b.style.position="relative";var c=function(a,b,c,d){function e(a){return a.replace(/[^-\d\.]/g,"")}var f=c[d];"auto"!==f&&"0"!==e(f)&&(a.warn("An element that is positioned static has style."+d+"="+f+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};c(i,b,a,"top"),c(i,b,a,"right"),c(i,b,a,"bottom"),c(i,b,a,"left")}}function c(a,b,c,d){return a=a?a+"px":"0",b=b?b+"px":"0",c=c?c+"px":"0",d=d?d+"px":"0","left: "+a+"; top: "+b+"; right: "+d+"; bottom: "+c+";"}f("Injecting elements"),a();var d=k(b).container;d||(d=E());var e=m.width,g=m.height,h="position: absolute; overflow: hidden; z-index: -1; visibility: hidden; width: 100%; height: 100%; left: 0px; top: 0px;",j="position: absolute; overflow: hidden; z-index: -1; visibility: hidden; "+c(-(1+e),-(1+g),-g,-e),l="position: absolute; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;",n="position: absolute; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;",p="position: absolute; left: 0; top: 0;",q="position: absolute; width: 200%; height: 200%;",r=document.createElement("div"),s=document.createElement("div"),t=document.createElement("div"),u=document.createElement("div"),v=document.createElement("div"),w=document.createElement("div");r.style.cssText=h,r.className=o,s.className=o,s.style.cssText=j,t.style.cssText=l,u.style.cssText=p,v.style.cssText=n,w.style.cssText=q,t.appendChild(u),v.appendChild(w),s.appendChild(t),s.appendChild(v),r.appendChild(s),d.appendChild(r),D(t,"scroll",function(){k(b).onExpand&&k(b).onExpand()}),D(v,"scroll",function(){k(b).onShrink&&k(b).onShrink()})}function G(){function c(a,b,c){var d=u(a),e=y(b),f=z(c);d.style.width=e+"px",d.style.height=f+"px"}function e(d){var e=b.offsetWidth,g=b.offsetHeight;f("Storing current size",e,g),s(b,e,g),j.add(0,function(){if(a.debug){var d=b.offsetWidth,f=b.offsetHeight;(d!==e||f!==g)&&i.warn(l.get(b),"Scroll: Size changed before updating detector elements.")}c(b,e,g)}),j.add(1,function(){C(b,e,g)}),d&&j.add(2,d)}function g(){function a(){return void 0===k(b).lastNotifiedWidth}f("notifyListenersIfNeeded invoked");var c=k(b);return a()&&c.lastWidth===c.startSize.width&&c.lastHeight===c.startSize.height?f("Not notifying: Size is the same as the start size, and there has been no notification yet."):c.lastWidth===c.lastNotifiedWidth&&c.lastHeight===c.lastNotifiedHeight?f("Not notifying: Size already notified"):(f("Current size not notified, notifying..."),c.lastNotifiedWidth=c.lastWidth,c.lastNotifiedHeight=c.lastHeight,void d(k(b).listeners,function(a){a(b)}))}function m(){if(f("startanimation triggered."),h(b))return void f("Ignoring since element is still unrendered...");f("Element rendered.");var a=t(b),c=v(b);(0===a.scrollLeft||0===a.scrollTop||0===c.scrollLeft||0===c.scrollTop)&&(f("Scrollbars out of sync. Updating detector elements..."),e(g))}function n(){if(f("Scroll detected."),h(b))return void f("Scroll event fired while unrendered. Ignoring...");var a=b.offsetWidth,c=b.offsetHeight;a!==b.lastWidth||c!==b.lastHeight?(f("Element size changed."),e(g)):f("Element size has not changed ("+a+"x"+c+").")}f("registerListenersAndPositionElements invoked."),k(b).onRendered=m,k(b).onExpand=n,k(b).onShrink=n;var o=k(b).style;c(b,o.width,o.height)}function H(){f("finalizeDomMutation invoked.");var a=k(b).style;s(b,a.width,a.height),C(b,a.width,a.height)}function I(){c(b)}function J(){f("Installing..."),q(),p(),j.add(0,r),j.add(1,F),j.add(2,G),j.add(3,H),j.add(4,I)}c||(c=b,b=a,a=null),a=a||{},f("Making detectable..."),g(b)?(f("Element is detached"),E(),f("Waiting until element is attached..."),k(b).onRendered=function(){f("Element is now attached"),J()}):J()}function h(a){var b=k(a);a.removeChild(b.container),delete b.container}a=a||{};var i=a.reporter,j=a.batchProcessor,k=a.stateHandler.getState,l=a.idHandler;if(!j)throw new Error("Missing required dependency: batchProcessor");if(!i)throw new Error("Missing required dependency: reporter.");var m=b(),n="erd_scroll_detection_scrollbar_style",o="erd_scroll_detection_container";return c(n,o),{makeDetectable:g,addListener:f,uninstall:h}}},{"../collection-utils":4}],7:[function(a,b,c){"use strict";function d(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var e=a("./collection-utils").forEach,f=a("./element-utils"),g=a("./listener-handler"),h=a("./id-generator"),i=a("./id-handler"),j=a("./reporter"),k=a("./browser-detector"),l=a("batch-processor"),m=a("./state-handler"),n=a("./detection-strategy/object.js"),o=a("./detection-strategy/scroll.js");b.exports=function(a){function b(a,b,c){function f(a){var b=x.get(a);e(b,function(b){b(a)})}function g(a,b,c){x.add(b,c),a&&c(b)}function h(a){return Array.isArray(a)||void 0!==a.length}function i(a){if(Array.isArray(a))return a;var c=[];return e(b,function(a){c.push(a)}),c}function j(a){return a&&1===a.nodeType}if(c||(c=b,b=a,a={}),!b)throw new Error("At least one element required.");if(!c)throw new Error("Listener required.");if(j(b))b=[b];else{if(!h(b))return s.error("Invalid arguments. Must be a DOM element or a collection of DOM elements.");b=i(b)}var k=0,l=d(a,"callOnAdd",v.callOnAdd),n=d(a,"onReady",function(){}),o=d(a,"debug",v.debug);e(b,function(a){var d=p.get(a);return o&&s.log("Attaching listener to element",d,a),y.isDetectable(a)?(o&&s.log(d,"Already detecable, adding listener."),g(l,a,c),void k++):(o&&s.log(d,"Not detectable."),y.isBusy(a)?(o&&s.log(d,"System busy making it detectable"),g(l,a,c),B[d]=B[d]||[],void B[d].push(function(){k++,k===b.length&&n()})):(o&&s.log(d,"Making detectable..."),y.markBusy(a,!0),w.makeDetectable({debug:o},a,function(a){if(o&&s.log(d,"onElementDetectable"),y.markAsDetectable(a),y.markBusy(a,!1),w.addListener(a,f),g(l,a,c),m.getState(a).startSize){var h=a.offsetWidth,i=a.offsetHeight;(m.getState(a).startSize.width!==h||m.getState(a).startSize.height!==i)&&f(a)}k++,k===b.length&&n(),B[d]&&(e(B[d],function(a){a()}),delete B[d])})))}),k===b.length&&n()}function c(a){x.removeAllListeners(a),w.uninstall(a),m.cleanState(a)}a=a||{};var p=a.idHandler;if(!p){var q=h(),r=i({idGenerator:q,stateHandler:m});p=r}var s=a.reporter;if(!s){var t=s===!1;s=j(t)}var u=d(a,"batchProcessor",l({reporter:s})),v={};v.callOnAdd=!!d(a,"callOnAdd",!0),v.debug=!!d(a,"debug",!1);var w,x=g(p),y=f({stateHandler:m}),z=d(a,"strategy","object"),A={reporter:s,batchProcessor:u,stateHandler:m,idHandler:p};if("scroll"===z&&(k.isLegacyOpera()?(s.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."),z="object"):k.isIE(9)&&(s.warn("Scroll strategy is not supported on IE9. Changing to object strategy."),z="object")),"scroll"===z)w=o(A);else{if("object"!==z)throw new Error("Invalid strategy name: "+z);w=n(A)}var B={};return{listenTo:b,removeListener:x.removeListener,removeAllListeners:x.removeAllListeners,uninstall:c}}},{"./browser-detector":3,"./collection-utils":4,"./detection-strategy/object.js":5,"./detection-strategy/scroll.js":6,"./element-utils":8,"./id-generator":9,"./id-handler":10,"./listener-handler":11,"./reporter":12,"./state-handler":13,"batch-processor":1}],8:[function(a,b,c){"use strict";b.exports=function(a){function b(a){return!!f(a).isDetectable}function c(a){f(a).isDetectable=!0}function d(a){return!!f(a).busy}function e(a,b){f(a).busy=!!b}var f=a.stateHandler.getState;return{isDetectable:b,markAsDetectable:c,isBusy:d,markBusy:e}}},{}],9:[function(a,b,c){"use strict";b.exports=function(){function a(){return b++}var b=1;return{generate:a}}},{}],10:[function(a,b,c){"use strict";b.exports=function(a){function b(a,b){return b||d(a)||c(a),g(a).id}function c(a){var b=f.generate();return g(a).id=b,b}function d(a){return void 0!==g(a).id}function e(a){delete g(a).id}var f=a.idGenerator,g=a.stateHandler.getState;return{get:b,remove:e}}},{}],11:[function(a,b,c){"use strict";b.exports=function(a){function b(b){return f[a.get(b)]||[]}function c(b,c){var d=a.get(b);f[d]||(f[d]=[]),f[d].push(c)}function d(a,c){for(var d=b(a),e=0,f=d.length;f>e;++e)if(d[e]===c){d.splice(e,1);break}}function e(b){var c=f[a.get(b)];c&&(c.length=0)}var f={};return{get:b,add:c,removeListener:d,removeAllListeners:e}}},{}],12:[function(a,b,c){"use strict";b.exports=function(a){function b(){}var c={log:b,warn:b,error:b};if(!a&&window.console){var d=function(a,b){a[b]=function(){var a=console[b];if(a.apply)a.apply(console,arguments);else for(var c=0;c<arguments.length;c++)a(arguments[c])}};d(c,"log"),d(c,"warn"),d(c,"error")}return c}},{}],13:[function(a,b,c){"use strict";function d(a){return a[g]={},e(a)}function e(a){return a[g]||d(a)}function f(a){delete a[g]}var g="_erd";b.exports={initState:d,getState:e,cleanState:f}},{}]},{},[7])(7)}); |
@@ -68,3 +68,3 @@ "use strict"; | ||
browsers: [ | ||
"Chrome" | ||
// "Chrome" | ||
//, "IE8 - Win7", "IE10 - Win7", "IE11 - Win8.1" | ||
@@ -71,0 +71,0 @@ ], |
{ | ||
"name": "element-resize-detector", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "Resize event emitter for elements.", | ||
@@ -14,3 +14,3 @@ "homepage": "https://github.com/wnr/element-resize-detector", | ||
"dependencies": { | ||
"batch-processor": "^0.2.2" | ||
"batch-processor": "^1.0.0" | ||
}, | ||
@@ -17,0 +17,0 @@ "devDependencies": { |
# element-resize-detector | ||
Super-optimized cross-browser resize listener for elements. | ||
```npm install element-resize-detector``` | ||
``` | ||
npm install element-resize-detector | ||
``` | ||
### Include script | ||
## Usage | ||
Include the script in the browser: | ||
```html | ||
<!DOCTYPE hml> | ||
<html> | ||
<head></head> | ||
<body> | ||
<script src="node_modules/element-resize-detector/dist/element-resize-detector.min.js"></script> | ||
</body> | ||
</html> | ||
<script src="node_modules/element-resize-detector/dist/element-resize-detector.min.js"></script> | ||
``` | ||
This will create a global function ```elementResizeDetectorMaker```, which is the maker function that makes an element resize detector instance. | ||
This will create a global function `elementResieDetectorMaker`, which is the maker function that makes an element resize detector instance. | ||
You can also ```require``` it like so: | ||
You can also `require` it like so: | ||
```js | ||
@@ -26,20 +22,20 @@ var elementResizeDetectorMaker = require("element-resize-detector"); | ||
```js | ||
//With default options (will use the object-based approach). | ||
var erdDefault = elementResizeDetectorMaker(); | ||
// With default options (will use the object-based approach). | ||
var erd = elementResizeDetectorMaker(); | ||
//With the experimental super fast scroll-based approach. | ||
// With the experimental scroll-based approach. | ||
var erdUltraFast = elementResizeDetectorMaker({ | ||
strategy: "scroll" //<- For ultra performance. | ||
strategy: "scroll" //<- For ultra performance. | ||
}); | ||
``` | ||
### API | ||
## API | ||
#### listenTo(element, listener) | ||
### listenTo(element, listener) | ||
Listens to the element for resize events and calls the listener function with the element as argument on resize events. | ||
**Example usage:** | ||
```js | ||
erd.listenTo(document.getElementById("test"), function(element) { | ||
//Should probably use jQuery for the width and height for compability. | ||
var width = element.offsetWidth; | ||
@@ -51,17 +47,17 @@ var height = element.offsetHeight; | ||
#### removeListener(element, listener) | ||
### removeListener(element, listener) | ||
Removes the listener from the element. | ||
#### removeAllListeners(element) | ||
### removeAllListeners(element) | ||
Removes all listeners from the element, but does not completely remove the detector. Use this function if you may add listeners later and don't want the detector to have to initialize again. | ||
#### uninstall(element) | ||
### uninstall(element) | ||
Completely removes the detector and all listeners. | ||
**Caveats:** | ||
## Caveats | ||
1. If the element has ```display: static``` it will be changed to ```display: relative```. This means if you have unintentional ```top/right/bottom/left``` styles on the element (which was ignored when being ```static```) they will now be applied to the element. This will also mean that if there are any elements with ```position: absolute``` as children to the element, they will now be positioned relative to the element. | ||
2. An ```<object>``` element will be injected as a direct child to the element. It has ```position: absolute``` so it will not affect the page flow. It is also visibly hidden. | ||
1. If the element has `position: static` it will be changed to `position: relative`. Any unintentional `top/right/bottom/left/z-index` styles will therefore be applied and absolute positioned children will be positioned relative to the element. | ||
2. A hidden `<object>` element will be injected as a direct child to the element. | ||
### Credits | ||
## Credits | ||
This library is using the two approaches (scroll and object) as first described at [http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/). | ||
@@ -72,1 +68,10 @@ | ||
Please note that both approaches have been heavily reworked for better performance and robustness. | ||
## Changelog | ||
#### 1.1.0 | ||
* Supporting inline elements | ||
* Event-based solution for detecting attached/rendered events so that detached/unrendered elements can be listened to without polling | ||
* Now all changes that affects the offset size of an element are properly detected (such as padding and font-size). | ||
* Scroll is stabilized, and is the preferred strategy to use. The object strategy will be deprecated (and is currently only used for some legacy browsers such as IE9 and Opera 12). |
@@ -75,6 +75,8 @@ /** | ||
var style = getComputedStyle(element); | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
getState(element).startSizeStyle = { | ||
width: style.width, | ||
height: style.height | ||
getState(element).startSize = { | ||
width: width, | ||
height: height | ||
}; | ||
@@ -81,0 +83,0 @@ |
@@ -17,6 +17,7 @@ /** | ||
// The injected container needs to have a class, so that it may be styled with CSS (pseudo elements). | ||
var detectionContainerClass = "erd_scroll_detection_container"; | ||
if (!batchProcessor) { | ||
throw new Error("Missing required dependency: batchProcessor"); | ||
} | ||
if(!reporter) { | ||
if (!reporter) { | ||
throw new Error("Missing required dependency: reporter."); | ||
@@ -29,5 +30,61 @@ } | ||
// Inject the scrollbar styling that prevents them from appearing sometimes in Chrome. | ||
// The injected container needs to have a class, so that it may be styled with CSS (pseudo elements). | ||
var styleId = "erd_scroll_detection_scrollbar_style"; | ||
var detectionContainerClass = "erd_scroll_detection_container"; | ||
injectScrollStyle(styleId, detectionContainerClass); | ||
function getScrollbarSizes() { | ||
var width = 500; | ||
var height = 500; | ||
var child = document.createElement("div"); | ||
child.style.cssText = "position: absolute; width: " + width*2 + "px; height: " + height*2 + "px; visibility: hidden;"; | ||
var container = document.createElement("div"); | ||
container.style.cssText = "position: absolute; width: " + width + "px; height: " + height + "px; overflow: scroll; visibility: none; top: " + -width*3 + "px; left: " + -height*3 + "px; visibility: hidden;"; | ||
container.appendChild(child); | ||
document.body.insertBefore(container, document.body.firstChild); | ||
var widthSize = width - container.clientWidth; | ||
var heightSize = height - container.clientHeight; | ||
document.body.removeChild(container); | ||
return { | ||
width: widthSize, | ||
height: heightSize | ||
}; | ||
} | ||
function injectScrollStyle(styleId, containerClass) { | ||
function injectStyle(style, method) { | ||
method = method || function (element) { | ||
document.head.appendChild(element); | ||
}; | ||
var styleElement = document.createElement("style"); | ||
styleElement.innerHTML = style; | ||
styleElement.id = styleId; | ||
method(styleElement); | ||
return styleElement; | ||
} | ||
if (!document.getElementById(styleId)) { | ||
var containerAnimationClass = containerClass + "_animation"; | ||
var containerAnimationActiveClass = containerClass + "_animation_active"; | ||
var style = "/* Created by the element-resize-detector library. */\n"; | ||
style += "." + containerClass + " > div::-webkit-scrollbar { display: none; }\n\n"; | ||
style += "." + containerAnimationActiveClass + " { -webkit-animation-duration: 0.1s; animation-duration: 0.1s; -webkit-animation-name: " + containerAnimationClass + "; animation-name: " + containerAnimationClass + "; }\n"; | ||
style += "@-webkit-keyframes " + containerAnimationClass + " { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }\n"; | ||
style += "@keyframes " + containerAnimationClass + " { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }"; | ||
injectStyle(style); | ||
} | ||
} | ||
function addAnimationClass(element) { | ||
element.className += " " + detectionContainerClass + "_animation_active"; | ||
} | ||
/** | ||
@@ -69,365 +126,408 @@ * Adds a resize event listener to the element. | ||
args.unshift(idHandler.get(element), "Scroll: "); | ||
reporter.log.apply(null, args); | ||
if (reporter.log.apply) { | ||
reporter.log.apply(null, args); | ||
} else { | ||
for (var i = 0; i < args.length; i++) { | ||
reporter.log(args[i]); | ||
} | ||
} | ||
} | ||
} | ||
function isStyleResolved() { | ||
function isPxValue(length) { | ||
return length.indexOf("px") !== -1; | ||
function isDetached(element) { | ||
function isInDocument(element) { | ||
return element === element.ownerDocument.body || element.ownerDocument.body.contains(element); | ||
} | ||
return !isInDocument(element); | ||
} | ||
var style = getComputedStyle(element); | ||
function isUnrendered(element) { | ||
// Check the absolute positioned container since the top level container is display: inline. | ||
var container = getState(element).container.childNodes[0]; | ||
return getComputedStyle(container).width.indexOf("px") === -1; //Can only compute pixel value when rendered. | ||
} | ||
return style.position && isPxValue(style.width) && isPxValue(style.height); | ||
function getStyle() { | ||
// Some browsers only force layouts when actually reading the style properties of the style object, so make sure that they are all read here, | ||
// so that the user of the function can be sure that it will perform the layout here, instead of later (important for batching). | ||
var elementStyle = getComputedStyle(element); | ||
var style = {}; | ||
style.position = elementStyle.position; | ||
style.width = element.offsetWidth; | ||
style.height = element.offsetHeight; | ||
style.top = elementStyle.top; | ||
style.right = elementStyle.right; | ||
style.bottom = elementStyle.bottom; | ||
style.left = elementStyle.left; | ||
style.widthCSS = elementStyle.width; | ||
style.heightCSS = elementStyle.height; | ||
return style; | ||
} | ||
function install() { | ||
function getStyle() { | ||
// Some browsers only force layouts when actually reading the style properties of the style object, so make sure that they are all read here, | ||
// so that the user of the function can be sure that it will perform the layout here, instead of later (important for batching). | ||
var style = {}; | ||
var elementStyle = getComputedStyle(element); | ||
style.position = elementStyle.position; | ||
style.width = parseSize(elementStyle.width); | ||
style.height = parseSize(elementStyle.height); | ||
style.top = elementStyle.top; | ||
style.right = elementStyle.right; | ||
style.bottom = elementStyle.bottom; | ||
style.left = elementStyle.left; | ||
style.widthStyle = elementStyle.width; | ||
style.heightStyle = elementStyle.height; | ||
return style; | ||
} | ||
function storeStartSize() { | ||
var style = getStyle(); | ||
getState(element).startSize = { | ||
width: style.width, | ||
height: style.height | ||
}; | ||
debug("Element start size", getState(element).startSize); | ||
} | ||
function storeStartSize() { | ||
var style = getStyle(); | ||
getState(element).startSizeStyle = { | ||
width: style.widthStyle, | ||
height: style.heightStyle | ||
}; | ||
} | ||
function initListeners() { | ||
getState(element).listeners = []; | ||
} | ||
function initListeners() { | ||
getState(element).listeners = []; | ||
} | ||
function storeStyle() { | ||
debug("storeStyle invoked."); | ||
var style = getStyle(); | ||
getState(element).style = style; | ||
} | ||
debug("Installing scroll elements..."); | ||
function storeCurrentSize(element, width, height) { | ||
getState(element).lastWidth = width; | ||
getState(element).lastHeight = height; | ||
} | ||
storeStartSize(); | ||
initListeners(); | ||
function getExpandElement(element) { | ||
return getState(element).container.childNodes[0].childNodes[0].childNodes[0]; | ||
} | ||
debug("Element start size", getState(element).startSizeStyle); | ||
function getExpandChildElement(element) { | ||
return getExpandElement(element).childNodes[0]; | ||
} | ||
function storeStyle() { | ||
debug("storeStyle invoked."); | ||
function getShrinkElement(element) { | ||
return getState(element).container.childNodes[0].childNodes[0].childNodes[1]; | ||
} | ||
// Style is to be retrieved in the first level (before mutating the DOM) so that a forced layout is avoided later. | ||
var style = getStyle(); | ||
getState(element).style = style; | ||
function getWidthOffset() { | ||
return 2 * scrollbarSizes.width + 1; | ||
} | ||
function getHeightOffset() { | ||
return 2 * scrollbarSizes.height + 1; | ||
} | ||
function getExpandWidth(width) { | ||
return width + 10 + getWidthOffset(); | ||
} | ||
function getExpandHeight(height) { | ||
return height + 10 + getHeightOffset(); | ||
} | ||
function getShrinkWidth(width) { | ||
return width * 2 + getWidthOffset(); | ||
} | ||
function getShrinkHeight(height) { | ||
return height * 2 + getHeightOffset(); | ||
} | ||
function positionScrollbars(element, width, height) { | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
var shrinkWidth = getShrinkWidth(width); | ||
var shrinkHeight = getShrinkHeight(height); | ||
expand.scrollLeft = expandWidth; | ||
expand.scrollTop = expandHeight; | ||
shrink.scrollLeft = shrinkWidth; | ||
shrink.scrollTop = shrinkHeight; | ||
} | ||
function addEvent(el, name, cb) { | ||
if (el.addEventListener) { | ||
el.addEventListener(name, cb); | ||
} else if(el.attachEvent) { | ||
el.attachEvent("on" + name, cb); | ||
} else { | ||
return reporter.error("[scroll] Don't know how to add event listeners."); | ||
} | ||
} | ||
function mutateDom() { | ||
debug("mutateDom invoked."); | ||
function injectContainerElement() { | ||
var container = getState(element).container; | ||
if (!container) { | ||
container = document.createElement("div"); | ||
container.className = detectionContainerClass; | ||
container.style.cssText = "visibility: hidden; display: inline; width: 0px; height: 0px; z-index: -1; overflow: hidden;"; | ||
getState(element).container = container; | ||
addAnimationClass(container); | ||
element.appendChild(container); | ||
addEvent(container, "animationstart", function onAnimationStart () { | ||
getState(element).onRendered && getState(element).onRendered(); | ||
}); | ||
} | ||
return container; | ||
} | ||
function injectScrollElements() { | ||
function alterPositionStyles() { | ||
var style = getState(element).style; | ||
function alterPositionStyles() { | ||
if(style.position === "static") { | ||
element.style.position = "relative"; | ||
if(style.position === "static") { | ||
element.style.position = "relative"; | ||
var removeRelativeStyles = function(reporter, element, style, property) { | ||
function getNumericalValue(value) { | ||
return value.replace(/[^-\d\.]/g, ""); | ||
} | ||
var removeRelativeStyles = function(reporter, element, style, property) { | ||
function getNumericalValue(value) { | ||
return value.replace(/[^-\d\.]/g, ""); | ||
} | ||
var value = style[property]; | ||
var value = style[property]; | ||
if(value !== "auto" && getNumericalValue(value) !== "0") { | ||
reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element); | ||
element.style[property] = 0; | ||
} | ||
}; | ||
if(value !== "auto" && getNumericalValue(value) !== "0") { | ||
reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element); | ||
element.style[property] = 0; | ||
} | ||
}; | ||
//Check so that there are no accidental styles that will make the element styled differently now that is is relative. | ||
//If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway). | ||
removeRelativeStyles(reporter, element, style, "top"); | ||
removeRelativeStyles(reporter, element, style, "right"); | ||
removeRelativeStyles(reporter, element, style, "bottom"); | ||
removeRelativeStyles(reporter, element, style, "left"); | ||
} | ||
//Check so that there are no accidental styles that will make the element styled differently now that is is relative. | ||
//If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway). | ||
removeRelativeStyles(reporter, element, style, "top"); | ||
removeRelativeStyles(reporter, element, style, "right"); | ||
removeRelativeStyles(reporter, element, style, "bottom"); | ||
removeRelativeStyles(reporter, element, style, "left"); | ||
} | ||
} | ||
function getContainerCssText(left, top, bottom, right) { | ||
left = (!left ? "0" : (left + "px")); | ||
top = (!top ? "0" : (top + "px")); | ||
bottom = (!bottom ? "0" : (bottom + "px")); | ||
right = (!right ? "0" : (right + "px")); | ||
function getTopBottomBottomRightCssText(left, top, bottom, right) { | ||
left = (!left ? "0" : (left + "px")); | ||
top = (!top ? "0" : (top + "px")); | ||
bottom = (!bottom ? "0" : (bottom + "px")); | ||
right = (!right ? "0" : (right + "px")); | ||
return "position: absolute; left: " + left + "; top: " + top + "; right: " + right + "; bottom: " + bottom + "; overflow: scroll; z-index: -1; visibility: hidden;"; | ||
} | ||
return "left: " + left + "; top: " + top + "; right: " + right + "; bottom: " + bottom + ";"; | ||
} | ||
alterPositionStyles(style); | ||
debug("Injecting elements"); | ||
var scrollbarWidth = scrollbarSizes.width; | ||
var scrollbarHeight = scrollbarSizes.height; | ||
var containerStyle = getContainerCssText(-(1 + scrollbarWidth), -(1 + scrollbarHeight), -scrollbarHeight, -scrollbarWidth); | ||
var shrinkExpandstyle = getContainerCssText(0, 0, -scrollbarHeight, -scrollbarWidth); | ||
var shrinkExpandChildStyle = "position: absolute; left: 0; top: 0;"; | ||
alterPositionStyles(); | ||
var container = document.createElement("div"); | ||
var expand = document.createElement("div"); | ||
var expandChild = document.createElement("div"); | ||
var shrink = document.createElement("div"); | ||
var shrinkChild = document.createElement("div"); | ||
var rootContainer = getState(element).container; | ||
container.className = detectionContainerClass; | ||
container.style.cssText = containerStyle; | ||
expand.style.cssText = shrinkExpandstyle; | ||
expandChild.style.cssText = shrinkExpandChildStyle; | ||
shrink.style.cssText = shrinkExpandstyle; | ||
shrinkChild.style.cssText = shrinkExpandChildStyle + " width: 200%; height: 200%;"; | ||
if (!rootContainer) { | ||
rootContainer = injectContainerElement(); | ||
} | ||
expand.appendChild(expandChild); | ||
shrink.appendChild(shrinkChild); | ||
container.appendChild(expand); | ||
container.appendChild(shrink); | ||
element.appendChild(container); | ||
getState(element).element = container; | ||
// Due to this WebKit bug https://bugs.webkit.org/show_bug.cgi?id=80808 (currently fixed in Blink, but still present in WebKit browsers such as Safari), | ||
// we need to inject two containers, one that is width/height 100% and another that is left/top -1px so that the final container always is 1x1 pixels bigger than | ||
// the targeted element. | ||
// When the bug is resolved, "containerContainer" may be removed. | ||
function handleScroll() { | ||
function changed() { | ||
var elementStyle = getComputedStyle(element); | ||
var width = parseSize(elementStyle.width); | ||
var height = parseSize(elementStyle.height); | ||
// The outer container can occasionally be less wide than the targeted when inside inline elements element in WebKit (see https://bugs.webkit.org/show_bug.cgi?id=152980). | ||
// This should be no problem since the inner container either way makes sure the injected scroll elements are at least 1x1 px. | ||
debug("Storing current size", width, height); | ||
var scrollbarWidth = scrollbarSizes.width; | ||
var scrollbarHeight = scrollbarSizes.height; | ||
var containerContainerStyle = "position: absolute; overflow: hidden; z-index: -1; visibility: hidden; width: 100%; height: 100%; left: 0px; top: 0px;"; | ||
var containerStyle = "position: absolute; overflow: hidden; z-index: -1; visibility: hidden; " + getTopBottomBottomRightCssText(-(1 + scrollbarWidth), -(1 + scrollbarHeight), -scrollbarHeight, -scrollbarWidth); | ||
var expandStyle = "position: absolute; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;"; | ||
var shrinkStyle = "position: absolute; overflow: scroll; z-index: -1; visibility: hidden; width: 100%; height: 100%;"; | ||
var expandChildStyle = "position: absolute; left: 0; top: 0;"; | ||
var shrinkChildStyle = "position: absolute; width: 200%; height: 200%;"; | ||
// Store the size of the element sync here, so that multiple scroll events may be ignored in the event listeners. | ||
// Otherwise the if-check in handleScroll is useless. | ||
storeCurrentSize(element, width, height); | ||
var containerContainer = document.createElement("div"); | ||
var container = document.createElement("div"); | ||
var expand = document.createElement("div"); | ||
var expandChild = document.createElement("div"); | ||
var shrink = document.createElement("div"); | ||
var shrinkChild = document.createElement("div"); | ||
batchProcessor.add(function updateDetectorElements() { | ||
if (options.debug) { | ||
var style = getComputedStyle(element); | ||
var w = parseSize(style.width); | ||
var h = parseSize(style.height); | ||
containerContainer.style.cssText = containerContainerStyle; | ||
containerContainer.className = detectionContainerClass; | ||
container.className = detectionContainerClass; | ||
container.style.cssText = containerStyle; | ||
expand.style.cssText = expandStyle; | ||
expandChild.style.cssText = expandChildStyle; | ||
shrink.style.cssText = shrinkStyle; | ||
shrinkChild.style.cssText = shrinkChildStyle; | ||
if (w !== width || h !== height) { | ||
reporter.warn(idHandler.get(element), "Scroll: Size changed before updating detector elements."); | ||
} | ||
} | ||
expand.appendChild(expandChild); | ||
shrink.appendChild(shrinkChild); | ||
container.appendChild(expand); | ||
container.appendChild(shrink); | ||
containerContainer.appendChild(container); | ||
rootContainer.appendChild(containerContainer); | ||
updateChildSizes(element, width, height); | ||
}); | ||
addEvent(expand, "scroll", function onExpandScroll() { | ||
getState(element).onExpand && getState(element).onExpand(); | ||
}); | ||
batchProcessor.add(1, function updateScrollbars() { | ||
positionScrollbars(element, width, height); | ||
forEach(getState(element).listeners, function (listener) { | ||
listener(element); | ||
}); | ||
}); | ||
} | ||
addEvent(shrink, "scroll", function onShrinkScroll() { | ||
getState(element).onShrink && getState(element).onShrink(); | ||
}); | ||
} | ||
debug("Scroll detected."); | ||
function registerListenersAndPositionElements() { | ||
debug("registerListenersAndPositionElements invoked."); | ||
var style = getComputedStyle(element); | ||
var width = parseSize(style.width); | ||
var height = parseSize(style.height); | ||
function updateChildSizes(element, width, height) { | ||
var expandChild = getExpandChildElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
expandChild.style.width = expandWidth + "px"; | ||
expandChild.style.height = expandHeight + "px"; | ||
} | ||
if (width !== element.lastWidth || height !== element.lastHeight) { | ||
debug("Element size changed."); | ||
changed(); | ||
function updateDetectorElements(done) { | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
debug("Storing current size", width, height); | ||
// Store the size of the element sync here, so that multiple scroll events may be ignored in the event listeners. | ||
// Otherwise the if-check in handleScroll is useless. | ||
storeCurrentSize(element, width, height); | ||
batchProcessor.add(0, function performUpdateChildSizes() { | ||
if (options.debug) { | ||
var w = element.offsetWidth; | ||
var h = element.offsetHeight; | ||
if (w !== width || h !== height) { | ||
reporter.warn(idHandler.get(element), "Scroll: Size changed before updating detector elements."); | ||
} | ||
} | ||
} | ||
addEvent(expand, "scroll", function onExpand() { | ||
handleScroll(); | ||
updateChildSizes(element, width, height); | ||
}); | ||
addEvent(shrink, "scroll", function onShrink() { | ||
handleScroll(); | ||
batchProcessor.add(1, function updateScrollbars() { | ||
positionScrollbars(element, width, height); | ||
}); | ||
updateChildSizes(element, style.width, style.height); | ||
if (done) { | ||
batchProcessor.add(2, done); | ||
} | ||
} | ||
function finalizeDomMutation() { | ||
debug("finalizeDomMutation invoked."); | ||
var style = getState(element).style; | ||
storeCurrentSize(element, style.width, style.height); | ||
positionScrollbars(element, style.width, style.height); | ||
function areElementsInjected() { | ||
return !!getState(element).container; | ||
} | ||
function ready() { | ||
callback(element); | ||
} | ||
function notifyListenersIfNeeded() { | ||
function isFirstNotify() { | ||
return getState(element).lastNotifiedWidth === undefined; | ||
} | ||
if(batchProcessor) { | ||
batchProcessor.add(0, storeStyle); | ||
batchProcessor.add(1, mutateDom); | ||
batchProcessor.add(2, finalizeDomMutation); | ||
batchProcessor.add(3, ready); | ||
} else { | ||
storeStyle(); | ||
mutateDom(); | ||
finalizeDomMutation(); | ||
ready(); | ||
} | ||
} | ||
debug("notifyListenersIfNeeded invoked"); | ||
debug("Making detectable..."); | ||
var state = getState(element); | ||
// Only install the strategy if the style has been resolved (this does not always mean that the element is attached). | ||
if (isStyleResolved()) { | ||
debug("Style resolved"); | ||
install(); | ||
} else { | ||
debug("Style not resolved"); | ||
debug("Polling for style resolution..."); | ||
// Don't notify the if the current size is the start size, and this is the first notification. | ||
if (isFirstNotify() && state.lastWidth === state.startSize.width && state.lastHeight === state.startSize.height) { | ||
return debug("Not notifying: Size is the same as the start size, and there has been no notification yet."); | ||
} | ||
// Need to perform polling in order to detect when the element has been attached to the DOM. | ||
var timeout = setInterval(function () { | ||
if (isStyleResolved()) { | ||
debug("Poll. Style resolved."); | ||
install(); | ||
clearTimeout(timeout); | ||
} else { | ||
debug("Poll. Style not resolved."); | ||
// Don't notify if the size already has been notified. | ||
if (state.lastWidth === state.lastNotifiedWidth && state.lastHeight === state.lastNotifiedHeight) { | ||
return debug("Not notifying: Size already notified"); | ||
} | ||
}, 50); | ||
} | ||
} | ||
function getExpandElement(element) { | ||
return getState(element).element.childNodes[0]; | ||
} | ||
function getExpandChildElement(element) { | ||
return getExpandElement(element).childNodes[0]; | ||
} | ||
debug("Current size not notified, notifying..."); | ||
state.lastNotifiedWidth = state.lastWidth; | ||
state.lastNotifiedHeight = state.lastHeight; | ||
forEach(getState(element).listeners, function (listener) { | ||
listener(element); | ||
}); | ||
} | ||
function getShrinkElement(element) { | ||
return getState(element).element.childNodes[1]; | ||
} | ||
function handleRender() { | ||
debug("startanimation triggered."); | ||
function getWidthOffset() { | ||
return 2 * scrollbarSizes.width + 1; | ||
} | ||
if (isUnrendered(element)) { | ||
debug("Ignoring since element is still unrendered..."); | ||
return; | ||
} | ||
function getHeightOffset() { | ||
return 2 * scrollbarSizes.height + 1; | ||
} | ||
debug("Element rendered."); | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
if (expand.scrollLeft === 0 || expand.scrollTop === 0 || shrink.scrollLeft === 0 || shrink.scrollTop === 0) { | ||
debug("Scrollbars out of sync. Updating detector elements..."); | ||
updateDetectorElements(notifyListenersIfNeeded); | ||
} | ||
} | ||
function getExpandWidth(width) { | ||
return width + 10 + getWidthOffset(); | ||
} | ||
function handleScroll() { | ||
debug("Scroll detected."); | ||
function getExpandHeight(height) { | ||
return height + 10 + getHeightOffset(); | ||
} | ||
if (isUnrendered(element)) { | ||
// Element is still unrendered. Skip this scroll event. | ||
debug("Scroll event fired while unrendered. Ignoring..."); | ||
return; | ||
} | ||
function getShrinkWidth(width) { | ||
return width * 2 + getWidthOffset(); | ||
} | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
function getShrinkHeight(height) { | ||
return height * 2 + getHeightOffset(); | ||
} | ||
if (width !== element.lastWidth || height !== element.lastHeight) { | ||
debug("Element size changed."); | ||
updateDetectorElements(notifyListenersIfNeeded); | ||
} else { | ||
debug("Element size has not changed (" + width + "x" + height + ")."); | ||
} | ||
} | ||
function updateChildSizes(element, width, height) { | ||
var expandChild = getExpandChildElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
expandChild.style.width = expandWidth + "px"; | ||
expandChild.style.height = expandHeight + "px"; | ||
} | ||
getState(element).onRendered = handleRender; | ||
getState(element).onExpand = handleScroll; | ||
getState(element).onShrink = handleScroll; | ||
function storeCurrentSize(element, width, height) { | ||
element.lastWidth = width; | ||
element.lastHeight = height; | ||
} | ||
var style = getState(element).style; | ||
updateChildSizes(element, style.width, style.height); | ||
} | ||
function positionScrollbars(element, width, height) { | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
var expandWidth = getExpandWidth(width); | ||
var expandHeight = getExpandHeight(height); | ||
var shrinkWidth = getShrinkWidth(width); | ||
var shrinkHeight = getShrinkHeight(height); | ||
expand.scrollLeft = expandWidth; | ||
expand.scrollTop = expandHeight; | ||
shrink.scrollLeft = shrinkWidth; | ||
shrink.scrollTop = shrinkHeight; | ||
} | ||
function finalizeDomMutation() { | ||
debug("finalizeDomMutation invoked."); | ||
function addEvent(el, name, cb) { | ||
if (el.attachEvent) { | ||
el.attachEvent("on" + name, cb); | ||
} else { | ||
el.addEventListener(name, cb); | ||
var style = getState(element).style; | ||
storeCurrentSize(element, style.width, style.height); | ||
positionScrollbars(element, style.width, style.height); | ||
} | ||
} | ||
function removeEvent(el, name, cb) { | ||
if(el.attachEvent) { | ||
el.detachEvent("on" + name, cb); | ||
} else { | ||
el.removeEventListener(name, cb); | ||
function ready() { | ||
callback(element); | ||
} | ||
} | ||
function parseSize(size) { | ||
return parseFloat(size.replace(/px/, "")); | ||
} | ||
function install() { | ||
debug("Installing..."); | ||
initListeners(); | ||
storeStartSize(); | ||
function getScrollbarSizes() { | ||
var width = 500; | ||
var height = 500; | ||
batchProcessor.add(0, storeStyle); | ||
batchProcessor.add(1, injectScrollElements); | ||
batchProcessor.add(2, registerListenersAndPositionElements); | ||
batchProcessor.add(3, finalizeDomMutation); | ||
batchProcessor.add(4, ready); | ||
} | ||
var child = document.createElement("div"); | ||
child.style.cssText = "position: absolute; width: " + width*2 + "px; height: " + height*2 + "px; visibility: hidden;"; | ||
debug("Making detectable..."); | ||
var container = document.createElement("div"); | ||
container.style.cssText = "position: absolute; width: " + width + "px; height: " + height + "px; overflow: scroll; visibility: none; top: " + -width*3 + "px; left: " + -height*3 + "px; visibility: hidden;"; | ||
if (isDetached(element)) { | ||
debug("Element is detached"); | ||
container.appendChild(child); | ||
injectContainerElement(); | ||
document.body.insertBefore(container, document.body.firstChild); | ||
debug("Waiting until element is attached..."); | ||
var widthSize = width - container.clientWidth; | ||
var heightSize = height - container.clientHeight; | ||
document.body.removeChild(container); | ||
return { | ||
width: widthSize, | ||
height: heightSize | ||
}; | ||
} | ||
function injectScrollStyle(styleId, containerClass) { | ||
function injectStyle(style, method) { | ||
method = method || function (element) { | ||
document.head.appendChild(element); | ||
getState(element).onRendered = function () { | ||
debug("Element is now attached"); | ||
install(); | ||
}; | ||
var styleElement = document.createElement("style"); | ||
styleElement.innerHTML = style; | ||
styleElement.id = styleId; | ||
method(styleElement); | ||
return styleElement; | ||
} else { | ||
install(); | ||
} | ||
if (!document.getElementById(styleId)) { | ||
var style = "/* Created by the element-resize-detector library. */\n"; | ||
style += "." + containerClass + " > div::-webkit-scrollbar { display: none; }"; | ||
injectStyle(style); | ||
} | ||
} | ||
function uninstall(element) { | ||
//TODO: This should also delete the added state object of the element. | ||
var state = getState(element); | ||
element.removeChild(state.element); | ||
delete state.element; | ||
element.removeChild(state.container); | ||
delete state.container; | ||
} | ||
@@ -434,0 +534,0 @@ |
@@ -90,5 +90,10 @@ "use strict"; | ||
if(desiredStrategy === "scroll" && browserDetector.isLegacyOpera()) { | ||
reporter.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."); | ||
desiredStrategy = "object"; | ||
if(desiredStrategy === "scroll") { | ||
if (browserDetector.isLegacyOpera()) { | ||
reporter.warn("Scroll strategy is not supported on legacy Opera. Changing to object strategy."); | ||
desiredStrategy = "object"; | ||
} else if (browserDetector.isIE(9)) { | ||
reporter.warn("Scroll strategy is not supported on IE9. Changing to object strategy."); | ||
desiredStrategy = "object"; | ||
} | ||
} | ||
@@ -174,2 +179,3 @@ | ||
// Convert collection to array for plugins. | ||
// TODO: May want to check so that all the elements in the collection are valid elements. | ||
elements = toArray(elements); | ||
@@ -223,5 +229,9 @@ } else { | ||
// so that a resize event may be emitted. | ||
var style = getComputedStyle(element); | ||
if (stateHandler.getState(element).startSizeStyle.width !== style.width || stateHandler.getState(element).startSizeStyle.height !== style.height) { | ||
onResizeCallback(element); | ||
// Having the startSize object is optional (since it does not make sense in some cases such as unrendered elements), so check for its existance before. | ||
if (stateHandler.getState(element).startSize) { | ||
var width = element.offsetWidth; | ||
var height = element.offsetHeight; | ||
if (stateHandler.getState(element).startSize.width !== width || stateHandler.getState(element).startSize.height !== height) { | ||
onResizeCallback(element); | ||
} | ||
} | ||
@@ -228,0 +238,0 @@ |
@@ -26,3 +26,10 @@ "use strict"; | ||
reporter[name] = function reporterProxy() { | ||
console[name].apply(console, arguments); | ||
var f = console[name]; | ||
if (f.apply) { //IE9 does not support console.log.apply :) | ||
f.apply(console, arguments); | ||
} else { | ||
for (var i = 0; i < arguments.length; i++) { | ||
f(arguments[i]); | ||
} | ||
} | ||
}; | ||
@@ -29,0 +36,0 @@ }; |
@@ -81,3 +81,3 @@ /* global describe:false, it:false, beforeEach:false, expect:false, elementResizeDetectorMaker:false, _:false, $:false, jasmine:false */ | ||
function listenToTest(strategy) { | ||
describe("listenTo (" + strategy + ")", function() { | ||
describe("[" + strategy + "] listenTo", function() { | ||
it("should be able to attach a listener to an element", function(done) { | ||
@@ -307,5 +307,50 @@ var erd = elementResizeDetectorMaker({ | ||
it("should call listener on add if options is set to true", function(done) { | ||
describe("options.callOnAdd", function() { | ||
it("should be true default and call all functions when listenTo succeeds", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var listener = jasmine.createSpy("listener"); | ||
var listener2 = jasmine.createSpy("listener2"); | ||
erd.listenTo($("#test")[0], listener); | ||
erd.listenTo($("#test")[0], listener2); | ||
setTimeout(function() { | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener2).toHaveBeenCalledWith($("#test")[0]); | ||
listener.calls.reset(); | ||
listener2.calls.reset(); | ||
$("#test").width(300); | ||
}, 200); | ||
setTimeout(function() { | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener2).toHaveBeenCalledWith($("#test")[0]); | ||
done(); | ||
}, 400); | ||
}); | ||
it("should call listener multiple times when listening to multiple elements", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var listener1 = jasmine.createSpy("listener1"); | ||
erd.listenTo($("#test, #test2"), listener1); | ||
setTimeout(function() { | ||
expect(listener1).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener1).toHaveBeenCalledWith($("#test2")[0]); | ||
done(); | ||
}, 200); | ||
}); | ||
}); | ||
it("should call listener if the element is changed synchronously after listenTo", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: true, | ||
callOnAdd: false, | ||
reporter: reporter, | ||
@@ -316,7 +361,7 @@ strategy: strategy | ||
var listener1 = jasmine.createSpy("listener1"); | ||
erd.listenTo($("#test, #test2"), listener1); | ||
erd.listenTo($("#test"), listener1); | ||
$("#test").width(200); | ||
setTimeout(function() { | ||
expect(listener1).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener1).toHaveBeenCalledWith($("#test2")[0]); | ||
done(); | ||
@@ -326,3 +371,3 @@ }, 200); | ||
it("should call listener if the element is changed synchronously after listenTo", function(done) { | ||
it("should not emit resize when listenTo is called", function (done) { | ||
var erd = elementResizeDetectorMaker({ | ||
@@ -336,6 +381,5 @@ callOnAdd: false, | ||
erd.listenTo($("#test"), listener1); | ||
$("#test").width(200); | ||
setTimeout(function() { | ||
expect(listener1).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener1).not.toHaveBeenCalledWith($("#test")[0]); | ||
done(); | ||
@@ -345,3 +389,3 @@ }, 200); | ||
it("should be able to install into elements that are detached from the DOM", function(done) { | ||
it("should not emit resize event even though the element is back to its start size", function (done) { | ||
var erd = elementResizeDetectorMaker({ | ||
@@ -353,13 +397,15 @@ callOnAdd: false, | ||
var listener1 = jasmine.createSpy("listener1"); | ||
var div = document.createElement("div"); | ||
div.style.width = "100%"; | ||
div.style.height = "100%"; | ||
erd.listenTo(div, listener1); | ||
var listener = jasmine.createSpy("listener1"); | ||
$("#test").width(200); | ||
erd.listenTo($("#test"), listener); | ||
setTimeout(function () { | ||
$("#test")[0].appendChild(div); | ||
setTimeout(function() { | ||
expect(listener).not.toHaveBeenCalledWith($("#test")[0]); | ||
listener.calls.reset(); | ||
$("#test").width(100); | ||
}, 200); | ||
setTimeout(function () { | ||
setTimeout(function() { | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
listener.calls.reset(); | ||
$("#test").width(200); | ||
@@ -369,3 +415,3 @@ }, 400); | ||
setTimeout(function() { | ||
expect(listener1).toHaveBeenCalledWith(div); | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
done(); | ||
@@ -437,44 +483,50 @@ }, 600); | ||
}); | ||
}); | ||
} | ||
describe("element-resize-detector", function() { | ||
beforeEach(function() { | ||
//This messed with tests in IE8. | ||
//TODO: Investigate why, because it would be nice to have instead of the current solution. | ||
//loadFixtures("element-resize-detector_fixture.html"); | ||
$("#fixtures").html("<div id=test></div><div id=test2></div>"); | ||
}); | ||
it("should be able to install into elements that are detached from the DOM", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
describe("elementResizeDetectorMaker", function() { | ||
it("should be globally defined", function() { | ||
expect(elementResizeDetectorMaker).toBeFunction(); | ||
}); | ||
var listener1 = jasmine.createSpy("listener1"); | ||
var div = document.createElement("div"); | ||
div.style.width = "100%"; | ||
div.style.height = "100%"; | ||
erd.listenTo(div, listener1); | ||
it("should create an element-resize-detector instance", function() { | ||
var erd = elementResizeDetectorMaker(); | ||
setTimeout(function () { | ||
$("#test")[0].appendChild(div); | ||
}, 200); | ||
expect(erd).toBeObject(); | ||
expect(erd).toHaveMethod("listenTo"); | ||
setTimeout(function () { | ||
$("#test").width(200); | ||
}, 400); | ||
setTimeout(function() { | ||
expect(listener1).toHaveBeenCalledWith(div); | ||
done(); | ||
}, 600); | ||
}); | ||
}); | ||
describe("options.callOnAdd", function() { | ||
it("should be true default and call all functions when listenTo succeeds", function(done) { | ||
it("should detect resizes caused by padding and font-size changes", function (done) { | ||
var erd = elementResizeDetectorMaker({ | ||
reporter: reporter | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var listener = jasmine.createSpy("listener"); | ||
var listener2 = jasmine.createSpy("listener2"); | ||
$("#test").html("test"); | ||
$("#test").css("padding", "0px"); | ||
$("#test").css("font-size", "16px"); | ||
erd.listenTo($("#test")[0], listener); | ||
erd.listenTo($("#test")[0], listener2); | ||
erd.listenTo($("#test"), listener); | ||
$("#test").css("padding", "10px"); | ||
setTimeout(function() { | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener2).toHaveBeenCalledWith($("#test")[0]); | ||
listener.calls.reset(); | ||
listener2.calls.reset(); | ||
$("#test").width(300); | ||
$("#test").css("font-size", "20px"); | ||
}, 200); | ||
@@ -484,12 +536,103 @@ | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener2).toHaveBeenCalledWith($("#test")[0]); | ||
done(); | ||
}, 400); | ||
}); | ||
describe("should handle unrendered elements correctly", function (done) { | ||
it("when installing", function (done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
$("#test").html("<div id=\"inner\"></div>"); | ||
$("#test").css("display", "none"); | ||
var listener = jasmine.createSpy("listener"); | ||
erd.listenTo($("#inner"), listener); | ||
setTimeout(function () { | ||
expect(listener).not.toHaveBeenCalled(); | ||
$("#test").css("display", ""); | ||
}, 200); | ||
setTimeout(function () { | ||
expect(listener).toHaveBeenCalledWith($("#inner")[0]); | ||
listener.calls.reset(); | ||
$("#inner").width("300px"); | ||
}, 400); | ||
setTimeout(function () { | ||
expect(listener).toHaveBeenCalledWith($("#inner")[0]); | ||
listener.calls.reset(); | ||
done(); | ||
}, 600); | ||
}); | ||
it("when element gets unrendered after installation", function (done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
// The div is rendered to begin with. | ||
$("#test").html("<div id=\"inner\"></div>"); | ||
var listener = jasmine.createSpy("listener"); | ||
erd.listenTo($("#inner"), listener); | ||
// The it gets unrendered, and it changes width. | ||
setTimeout(function () { | ||
expect(listener).not.toHaveBeenCalled(); | ||
$("#test").css("display", "none"); | ||
$("#inner").width("300px"); | ||
}, 100); | ||
// Render the element again. | ||
setTimeout(function () { | ||
expect(listener).not.toHaveBeenCalled(); | ||
$("#test").css("display", ""); | ||
}, 200); | ||
// ERD should detect that the element has changed size as soon as it gets rendered again. | ||
setTimeout(function () { | ||
expect(listener).toHaveBeenCalledWith($("#inner")[0]); | ||
done(); | ||
}, 300); | ||
}); | ||
}); | ||
it("should handle inline elements correctly", function (done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
$("#test").html("<span id=\"inner\">test</span>"); | ||
var listener = jasmine.createSpy("listener"); | ||
erd.listenTo($("#inner"), listener); | ||
setTimeout(function () { | ||
expect(listener).not.toHaveBeenCalled(); | ||
$("#inner").append("testing testing"); | ||
}, 100); | ||
setTimeout(function () { | ||
expect(listener).toHaveBeenCalledWith($("#inner")[0]); | ||
done(); | ||
}, 200); | ||
}); | ||
}); | ||
} | ||
describe("resizeDetector.removeListener", function() { | ||
function removalTest(strategy) { | ||
describe("[" + strategy + "] resizeDetector.removeListener", function() { | ||
it("should remove listener from element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false | ||
callOnAdd: false, | ||
strategy: strategy | ||
}); | ||
@@ -518,6 +661,7 @@ | ||
describe("resizeDetector.removeAllListeners", function() { | ||
describe("[" + strategy + "] resizeDetector.removeAllListeners", function() { | ||
it("should remove all listeners from element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false | ||
callOnAdd: false, | ||
strategy: strategy | ||
}); | ||
@@ -546,3 +690,5 @@ | ||
it("should work for elements that don't have the detector installed", function() { | ||
var erd = elementResizeDetectorMaker(); | ||
var erd = elementResizeDetectorMaker({ | ||
strategy: strategy | ||
}); | ||
var $testElem = $("#test"); | ||
@@ -553,7 +699,7 @@ expect(erd.removeAllListeners.bind(erd, $testElem[0])).not.toThrow(); | ||
describe("resizeDetector.uninstall - object strategy", function() { | ||
describe("[" + strategy + "] resizeDetector.uninstall", function() { | ||
it("should completely remove detector from element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
strategy: "object" | ||
strategy: strategy | ||
}); | ||
@@ -580,36 +726,36 @@ | ||
}); | ||
} | ||
describe("resizeDetector.uninstall - scroll strategy", function() { | ||
it("should completely remove detector from element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
strategy: "scroll" | ||
}); | ||
describe("element-resize-detector", function() { | ||
beforeEach(function() { | ||
//This messed with tests in IE8. | ||
//TODO: Investigate why, because it would be nice to have instead of the current solution. | ||
//loadFixtures("element-resize-detector_fixture.html"); | ||
$("#fixtures").html("<div id=test></div><div id=test2></div>"); | ||
}); | ||
var $testElem = $("#test"); | ||
describe("elementResizeDetectorMaker", function() { | ||
it("should be globally defined", function() { | ||
expect(elementResizeDetectorMaker).toBeFunction(); | ||
}); | ||
var listener = jasmine.createSpy("listener"); | ||
it("should create an element-resize-detector instance", function() { | ||
var erd = elementResizeDetectorMaker(); | ||
erd.listenTo($testElem[0], listener); | ||
setTimeout(function() { | ||
erd.uninstall($testElem[0]); | ||
// detector element should be removed | ||
expect($testElem[0].childNodes.length).toBe(0); | ||
$testElem.width(300); | ||
}, 200); | ||
setTimeout(function() { | ||
expect(listener).not.toHaveBeenCalled(); | ||
done(); | ||
}, 400); | ||
expect(erd).toBeObject(); | ||
expect(erd).toHaveMethod("listenTo"); | ||
}); | ||
}); | ||
listenToTest("object"); | ||
// listenToTest("object"); | ||
// removalTest("object"); | ||
// | ||
// //Scroll only supported on non-opera browsers. | ||
// if(!window.opera) { | ||
// listenToTest("scroll"); | ||
// removalTest("scroll"); | ||
// } | ||
//Scroll only supported on non-opera browsers. | ||
if(!window.opera) { | ||
listenToTest("scroll"); | ||
} | ||
listenToTest("scroll"); | ||
removalTest("scroll"); | ||
}); |
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
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
201417
34
3609
75
+ Addedbatch-processor@1.0.0(transitive)
- Removedbatch-processor@0.2.2(transitive)
Updatedbatch-processor@^1.0.0