UNPKG

vue-uiv

Version:

Bootstrap 3 components implemented by Vue 2.

169 lines (155 loc) 4.82 kB
import { addClass, removeClass, EVENTS, on, off, getViewportSize, getClosest, getParents } from '../../utils/domUtils' import {nodeListToArray} from '../../utils/arrayUtils' function ScrollSpy (element, target = 'body', options = {}) { this.el = element this.opts = Object.assign({}, ScrollSpy.DEFAULTS, options) this.opts.target = target if (target === 'body') { this.scrollElement = window } else { this.scrollElement = document.querySelector(`[id=${target}]`) } this.selector = 'li > a' this.offsets = [] this.targets = [] this.activeTarget = null this.scrollHeight = 0 if (this.scrollElement) { this.refresh() this.process() } } ScrollSpy.DEFAULTS = { offset: 10, callback: (ele) => 0 } ScrollSpy.prototype.getScrollHeight = function () { return this.scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) } ScrollSpy.prototype.refresh = function () { this.offsets = [] this.targets = [] this.scrollHeight = this.getScrollHeight() let list = nodeListToArray(this.el.querySelectorAll(this.selector)) const isWindow = this.scrollElement === window list .map(ele => { const href = ele.getAttribute('href') if (/^#./.test(href)) { const doc = document.documentElement const rootEl = isWindow ? document : this.scrollElement const hrefEl = rootEl.querySelector(`[id='${href.slice(1)}']`) const windowScrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) const offset = isWindow ? hrefEl.getBoundingClientRect().top + windowScrollTop : hrefEl.offsetTop + this.scrollElement.scrollTop return [offset, href] } else { return null } }) .filter(item => item) .sort((a, b) => a[0] - b[0]) .forEach(item => { this.offsets.push(item[0]) this.targets.push(item[1]) }) // console.log(this.offsets, this.targets) } ScrollSpy.prototype.process = function () { const isWindow = this.scrollElement === window const scrollTop = (isWindow ? window.pageYOffset : this.scrollElement.scrollTop) + this.opts.offset const scrollHeight = this.getScrollHeight() const scrollElementHeight = isWindow ? getViewportSize().height : this.scrollElement.getBoundingClientRect().height const maxScroll = this.opts.offset + scrollHeight - scrollElementHeight const offsets = this.offsets const targets = this.targets const activeTarget = this.activeTarget let i if (this.scrollHeight !== scrollHeight) { this.refresh() } if (scrollTop >= maxScroll) { return activeTarget !== (i = targets[targets.length - 1]) && this.activate(i) } if (activeTarget && scrollTop < offsets[0]) { this.activeTarget = null return this.clear() } for (i = offsets.length; i--;) { activeTarget !== targets[i] && scrollTop >= offsets[i] && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) && this.activate(targets[i]) } } ScrollSpy.prototype.activate = function (target) { this.activeTarget = target this.clear() const selector = this.selector + '[data-target="' + target + '"],' + this.selector + '[href="' + target + '"]' const activeCallback = this.opts.callback let active = nodeListToArray(this.el.querySelectorAll(selector)) active.forEach(ele => { getParents(ele, 'li') .forEach(item => { addClass(item, 'active') activeCallback(item) }) if (getParents(ele, '.dropdown-menu').length) { addClass(getClosest(ele, 'li.dropdown'), 'active') } }) } ScrollSpy.prototype.clear = function () { let list = nodeListToArray(this.el.querySelectorAll(this.selector)) list.forEach(ele => { getParents(ele, '.active', this.opts.target).forEach(item => { removeClass(item, 'active') }) }) } const INSTANCE = '_uiv_scrollspy_instance' const events = [EVENTS.RESIZE, EVENTS.SCROLL] const bind = (el, binding) => { // console.log('bind') unbind(el) } const inserted = (el, binding) => { const scrollSpy = new ScrollSpy(el, binding.arg, binding.value) if (scrollSpy.scrollElement) { scrollSpy.handler = () => { scrollSpy.process() } events.forEach(event => { on(scrollSpy.scrollElement, event, scrollSpy.handler) }) } el[INSTANCE] = scrollSpy } const unbind = (el) => { // console.log('unbind') let instance = el[INSTANCE] if (instance && instance.scrollElement) { events.forEach(event => { off(instance.scrollElement, event, instance.handler) }) delete el[INSTANCE] } } const update = (el, binding) => { // console.log('update') if (binding.value !== binding.oldValue) { bind(el, binding) inserted(el, binding) } } export default {bind, unbind, update, inserted}