@7anshuai/vue-auto-sizer
Version:
Vue component that automatically adjusts the width and height of a single child.
246 lines (225 loc) • 8.28 kB
JavaScript
/**
* Detect Element Resize.
* https://github.com/sdecima/javascript-detect-element-resize
* Sebastian Decima
*
* Forked from version 0.5.3; includes the following modifications:
* 1) Guard against unsafe 'window' and 'document' references (to support SSR).
* 2) Defer initialization code via a top-level function wrapper (to support SSR).
* 3) Avoid unnecessary reflows by not measuring size for scroll events bubbling from children.
* 4) Add nonce for style element.
* 5) Added support for injecting custom window object
**/
export default function createDetectElementResize (nonce, hostWindow) {
// Check `document` and `window` in case of server-side rendering
var _window
if (typeof hostWindow !== 'undefined') {
_window = hostWindow
} else if (typeof window !== 'undefined') {
_window = window
} else if (typeof self !== 'undefined') {
_window = self
} else {
_window = global
}
var attachEvent =
typeof _window.document !== 'undefined' && _window.document.attachEvent
if (!attachEvent) {
var requestFrame = (function () {
var raf =
_window.requestAnimationFrame ||
_window.mozRequestAnimationFrame ||
_window.webkitRequestAnimationFrame ||
function (fn) {
return _window.setTimeout(fn, 20)
}
return function (fn) {
return raf(fn)
}
})()
var cancelFrame = (function () {
var cancel =
_window.cancelAnimationFrame ||
_window.mozCancelAnimationFrame ||
_window.webkitCancelAnimationFrame ||
_window.clearTimeout
return function (id) {
return cancel(id)
}
})()
var resetTriggers = function (element) {
var triggers = element.__resizeTriggers__
var expand = triggers.firstElementChild
var contract = triggers.lastElementChild
var expandChild = expand.firstElementChild
contract.scrollLeft = contract.scrollWidth
contract.scrollTop = contract.scrollHeight
expandChild.style.width = expand.offsetWidth + 1 + 'px'
expandChild.style.height = expand.offsetHeight + 1 + 'px'
expand.scrollLeft = expand.scrollWidth
expand.scrollTop = expand.scrollHeight
}
var checkTriggers = function (element) {
return (
element.offsetWidth !== element.__resizeLast__.width ||
element.offsetHeight !== element.__resizeLast__.height
)
}
var scrollListener = function (e) {
// Don't measure (which forces) reflow for scrolls that happen inside of children!
if (
e.target.className &&
typeof e.target.className.indexOf === 'function' &&
e.target.className.indexOf('contract-trigger') < 0 &&
e.target.className.indexOf('expand-trigger') < 0
) {
return
}
var element = this
resetTriggers(this)
if (this.__resizeRAF__) {
cancelFrame(this.__resizeRAF__)
}
this.__resizeRAF__ = requestFrame(function () {
if (checkTriggers(element)) {
element.__resizeLast__.width = element.offsetWidth
element.__resizeLast__.height = element.offsetHeight
element.__resizeListeners__.forEach(function (fn) {
fn.call(element, e)
})
}
})
}
/* Detect CSS Animations support to detect element display/re-attach */
var animation = false
var keyframeprefix = ''
var animationstartevent = 'animationstart'
var domPrefixes = 'Webkit Moz O ms'.split(' ')
var startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(
' '
)
var pfx = ''
var elm = _window.document.createElement('fakeelement')
if (elm.style.animationName !== undefined) {
animation = true
}
if (animation === false) {
for (var i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
pfx = domPrefixes[i]
keyframeprefix = '-' + pfx.toLowerCase() + '-'
animationstartevent = startEvents[i]
animation = true
break
}
}
}
var animationName = 'resizeanim'
var animationKeyframes =
'@' +
keyframeprefix +
'keyframes ' +
animationName +
' { from { opacity: 0; } to { opacity: 0; } } '
var animationStyle =
keyframeprefix + 'animation: 1ms ' + animationName + '; '
}
var createStyles = function (doc) {
if (!doc.getElementById('detectElementResize')) {
// opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360
var css =
(animationKeyframes || '') +
'.resize-triggers { ' +
(animationStyle || '') +
'visibility: hidden; opacity: 0; } ' +
'.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: " "; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; z-index: -1; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }'
var head = doc.head || doc.getElementsByTagName('head')[0]
var style = doc.createElement('style')
style.id = 'detectElementResize'
style.type = 'text/css'
if (nonce != null) {
style.setAttribute('nonce', nonce)
}
if (style.styleSheet) {
style.styleSheet.cssText = css
} else {
style.appendChild(doc.createTextNode(css))
}
head.appendChild(style)
}
}
var addResizeListener = function (element, fn) {
if (attachEvent) {
element.attachEvent('onresize', fn)
} else {
if (!element.__resizeTriggers__) {
var doc = element.ownerDocument
var elementStyle = _window.getComputedStyle(element)
if (elementStyle && elementStyle.position === 'static') {
element.style.position = 'relative'
}
createStyles(doc)
element.__resizeLast__ = {}
element.__resizeListeners__ = [];
(element.__resizeTriggers__ = doc.createElement('div')).className =
'resize-triggers'
var expandTrigger = doc.createElement('div')
expandTrigger.className = 'expand-trigger'
expandTrigger.appendChild(doc.createElement('div'))
var contractTrigger = doc.createElement('div')
contractTrigger.className = 'contract-trigger'
element.__resizeTriggers__.appendChild(expandTrigger)
element.__resizeTriggers__.appendChild(contractTrigger)
element.appendChild(element.__resizeTriggers__)
resetTriggers(element)
element.addEventListener('scroll', scrollListener, true)
/* Listen for a css animation to detect element display/re-attach */
if (animationstartevent) {
element.__resizeTriggers__.__animationListener__ = function animationListener (
e
) {
if (e.animationName === animationName) {
resetTriggers(element)
}
}
element.__resizeTriggers__.addEventListener(
animationstartevent,
element.__resizeTriggers__.__animationListener__
)
}
}
element.__resizeListeners__.push(fn)
}
}
var removeResizeListener = function (element, fn) {
if (attachEvent) {
element.detachEvent('onresize', fn)
} else {
element.__resizeListeners__.splice(
element.__resizeListeners__.indexOf(fn),
1
)
if (!element.__resizeListeners__.length) {
element.removeEventListener('scroll', scrollListener, true)
if (element.__resizeTriggers__.__animationListener__) {
element.__resizeTriggers__.removeEventListener(
animationstartevent,
element.__resizeTriggers__.__animationListener__
)
element.__resizeTriggers__.__animationListener__ = null
}
try {
element.__resizeTriggers__ = !element.removeChild(
element.__resizeTriggers__
)
} catch (e) {
// Preact compat; see developit/preact-compat/issues/228
}
}
}
}
return {
addResizeListener,
removeResizeListener
}
}