bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 75 custom components, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-ARIA accessibility markup.
275 lines (263 loc) • 6.82 kB
JavaScript
import Vue from '../../utils/vue';
import { BImg } from './img';
import { getComponentConfig } from '../../utils/config';
import { getBCR, eventOn, eventOff } from '../../utils/dom';
import { hasIntersectionObserverSupport } from '../../utils/env';
var NAME = 'BImgLazy';
var THROTTLE = 100;
var EVENT_OPTIONS = {
passive: true,
capture: false
};
export var props = {
src: {
type: String,
default: null,
required: true
},
alt: {
type: String,
default: null
},
width: {
type: [Number, String],
default: null
},
height: {
type: [Number, String],
default: null
},
blankSrc: {
// If null, a blank image is generated
type: String,
default: null
},
blankColor: {
type: String,
default: function _default() {
return getComponentConfig(NAME, 'blankColor');
}
},
blankWidth: {
type: [Number, String],
default: null
},
blankHeight: {
type: [Number, String],
default: null
},
show: {
type: Boolean,
default: false
},
fluid: {
type: Boolean,
default: false
},
fluidGrow: {
type: Boolean,
default: false
},
block: {
type: Boolean,
default: false
},
thumbnail: {
type: Boolean,
default: false
},
rounded: {
type: [Boolean, String],
default: false
},
left: {
type: Boolean,
default: false
},
right: {
type: Boolean,
default: false
},
center: {
type: Boolean,
default: false
},
offset: {
type: [Number, String],
default: 360
},
throttle: {
type: [Number, String],
default: THROTTLE
} // @vue/component
};
export var BImgLazy =
/*#__PURE__*/
Vue.extend({
name: NAME,
props: props,
data: function data() {
return {
isShown: false,
scrollTimeout: null,
observer: null
};
},
computed: {
computedSrc: function computedSrc() {
return !this.blankSrc || this.isShown ? this.src : this.blankSrc;
},
computedBlank: function computedBlank() {
return !(this.isShown || this.blankSrc);
},
computedWidth: function computedWidth() {
return this.isShown ? this.width : this.blankWidth || this.width;
},
computedHeight: function computedHeight() {
return this.isShown ? this.height : this.blankHeight || this.height;
}
},
watch: {
show: function show(newVal, oldVal) {
if (newVal !== oldVal) {
this.isShown = newVal;
if (!newVal) {
// Make sure listeners are re-enabled if img is force set to blank
this.setListeners(true);
}
}
},
isShown: function isShown(newVal, oldVal) {
if (newVal !== oldVal) {
// Update synched show prop
this.$emit('update:show', newVal);
}
}
},
created: function created() {
this.isShown = this.show;
},
mounted: function mounted() {
if (this.isShown) {
this.setListeners(false);
} else {
this.setListeners(true);
}
},
activated: function activated()
/* istanbul ignore next */
{
if (!this.isShown) {
this.setListeners(true);
}
},
deactivated: function deactivated()
/* istanbul ignore next */
{
this.setListeners(false);
},
beforeDestroy: function beforeDestroy() {
this.setListeners(false);
},
methods: {
setListeners: function setListeners(on) {
var _this = this;
if (this.scrollTimeout) {
clearTimeout(this.scrollTimeout);
this.scrollTimeout = null;
}
/* istanbul ignore next: JSDOM doen't support IntersectionObserver */
if (this.observer) {
this.observer.unobserve(this.$el);
this.observer.disconnect();
this.observer = null;
}
var winEvts = ['scroll', 'resize', 'orientationchange'];
winEvts.forEach(function (evt) {
return eventOff(window, evt, _this.onScroll, EVENT_OPTIONS);
});
eventOff(this.$el, 'load', this.checkView, EVENT_OPTIONS);
eventOff(document, 'transitionend', this.onScroll, EVENT_OPTIONS);
if (on) {
/* istanbul ignore if: JSDOM doen't support IntersectionObserver */
if (hasIntersectionObserverSupport) {
this.observer = new IntersectionObserver(this.doShow, {
root: null,
// viewport
rootMargin: "".concat(parseInt(this.offset, 10) || 0, "px"),
threshold: 0 // percent intersection
});
this.observer.observe(this.$el);
} else {
// Fallback to scroll/etc events
winEvts.forEach(function (evt) {
return eventOn(window, evt, _this.onScroll, EVENT_OPTIONS);
});
eventOn(this.$el, 'load', this.checkView, EVENT_OPTIONS);
eventOn(document, 'transitionend', this.onScroll, EVENT_OPTIONS);
}
}
},
doShow: function doShow(entries) {
if (entries && (entries[0].isIntersecting || entries[0].intersectionRatio > 0.0)) {
this.isShown = true;
this.setListeners(false);
}
},
checkView: function checkView() {
// check bounding box + offset to see if we should show
/* istanbul ignore next: should rarely occur */
if (this.isShown) {
this.setListeners(false);
return;
}
var offset = parseInt(this.offset, 10) || 0;
var docElement = document.documentElement;
var view = {
l: 0 - offset,
t: 0 - offset,
b: docElement.clientHeight + offset,
r: docElement.clientWidth + offset // JSDOM Doesn't support BCR, but we fake it in the tests
};
var box = getBCR(this.$el);
if (box.right >= view.l && box.bottom >= view.t && box.left <= view.r && box.top <= view.b) {
// image is in view (or about to be in view)
this.doShow([{
isIntersecting: true
}]);
}
},
onScroll: function onScroll() {
/* istanbul ignore if: should rarely occur */
if (this.isShown) {
this.setListeners(false);
} else {
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(this.checkView, parseInt(this.throttle, 10) || THROTTLE);
}
}
},
render: function render(h) {
return h(BImg, {
props: {
// Computed value props
src: this.computedSrc,
blank: this.computedBlank,
width: this.computedWidth,
height: this.computedHeight,
// Passthough props
alt: this.alt,
blankColor: this.blankColor,
fluid: this.fluid,
fluidGrow: this.fluidGrow,
block: this.block,
thumbnail: this.thumbnail,
rounded: this.rounded,
left: this.left,
right: this.right,
center: this.center
}
});
}
});
export default BImgLazy;