line_push/node_modules/vuetify/lib/components/VImg/VImg.js
2022-07-21 03:28:35 +00:00

261 lines
6.8 KiB
JavaScript

// Styles
import "../../../src/components/VImg/VImg.sass"; // Directives
import intersect from '../../directives/intersect'; // Components
import VResponsive from '../VResponsive'; // Mixins
import Themeable from '../../mixins/themeable'; // Utils
import mixins from '../../util/mixins';
import mergeData from '../../util/mergeData';
import { consoleWarn } from '../../util/console';
const hasIntersect = typeof window !== 'undefined' && 'IntersectionObserver' in window;
/* @vue/component */
export default mixins(VResponsive, Themeable).extend({
name: 'v-img',
directives: {
intersect
},
props: {
alt: String,
contain: Boolean,
eager: Boolean,
gradient: String,
lazySrc: String,
options: {
type: Object,
// For more information on types, navigate to:
// https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
default: () => ({
root: undefined,
rootMargin: undefined,
threshold: undefined
})
},
position: {
type: String,
default: 'center center'
},
sizes: String,
src: {
type: [String, Object],
default: ''
},
srcset: String,
transition: {
type: [Boolean, String],
default: 'fade-transition'
}
},
data() {
return {
currentSrc: '',
image: null,
isLoading: true,
calculatedAspectRatio: undefined,
naturalWidth: undefined,
hasError: false
};
},
computed: {
computedAspectRatio() {
return Number(this.normalisedSrc.aspect || this.calculatedAspectRatio);
},
normalisedSrc() {
return typeof this.src === 'string' ? {
src: this.src,
srcset: this.srcset,
lazySrc: this.lazySrc,
aspect: Number(this.aspectRatio || 0)
} : {
src: this.src.src,
srcset: this.srcset || this.src.srcset,
lazySrc: this.lazySrc || this.src.lazySrc,
aspect: Number(this.aspectRatio || this.src.aspect)
};
},
__cachedImage() {
if (!(this.normalisedSrc.src || this.normalisedSrc.lazySrc || this.gradient)) return [];
const backgroundImage = [];
const src = this.isLoading ? this.normalisedSrc.lazySrc : this.currentSrc;
if (this.gradient) backgroundImage.push(`linear-gradient(${this.gradient})`);
if (src) backgroundImage.push(`url("${src}")`);
const image = this.$createElement('div', {
staticClass: 'v-image__image',
class: {
'v-image__image--preload': this.isLoading,
'v-image__image--contain': this.contain,
'v-image__image--cover': !this.contain
},
style: {
backgroundImage: backgroundImage.join(', '),
backgroundPosition: this.position
},
key: +this.isLoading
});
/* istanbul ignore if */
if (!this.transition) return image;
return this.$createElement('transition', {
attrs: {
name: this.transition,
mode: 'in-out'
}
}, [image]);
}
},
watch: {
src() {
// Force re-init when src changes
if (!this.isLoading) this.init(undefined, undefined, true);else this.loadImage();
},
'$vuetify.breakpoint.width': 'getSrc'
},
mounted() {
this.init();
},
methods: {
init(entries, observer, isIntersecting) {
// If the current browser supports the intersection
// observer api, the image is not observable, and
// the eager prop isn't being used, do not load
if (hasIntersect && !isIntersecting && !this.eager) return;
if (this.normalisedSrc.lazySrc) {
const lazyImg = new Image();
lazyImg.src = this.normalisedSrc.lazySrc;
this.pollForSize(lazyImg, null);
}
/* istanbul ignore else */
if (this.normalisedSrc.src) this.loadImage();
},
onLoad() {
this.getSrc();
this.isLoading = false;
this.$emit('load', this.src);
},
onError() {
this.hasError = true;
this.$emit('error', this.src);
},
getSrc() {
/* istanbul ignore else */
if (this.image) this.currentSrc = this.image.currentSrc || this.image.src;
},
loadImage() {
const image = new Image();
this.image = image;
image.onload = () => {
/* istanbul ignore if */
if (image.decode) {
image.decode().catch(err => {
consoleWarn(`Failed to decode image, trying to render anyway\n\n` + `src: ${this.normalisedSrc.src}` + (err.message ? `\nOriginal error: ${err.message}` : ''), this);
}).then(this.onLoad);
} else {
this.onLoad();
}
};
image.onerror = this.onError;
this.hasError = false;
image.src = this.normalisedSrc.src;
this.sizes && (image.sizes = this.sizes);
this.normalisedSrc.srcset && (image.srcset = this.normalisedSrc.srcset);
this.aspectRatio || this.pollForSize(image);
this.getSrc();
},
pollForSize(img, timeout = 100) {
const poll = () => {
const {
naturalHeight,
naturalWidth
} = img;
if (naturalHeight || naturalWidth) {
this.naturalWidth = naturalWidth;
this.calculatedAspectRatio = naturalWidth / naturalHeight;
} else {
timeout != null && !this.hasError && setTimeout(poll, timeout);
}
};
poll();
},
genContent() {
const content = VResponsive.options.methods.genContent.call(this);
if (this.naturalWidth) {
this._b(content.data, 'div', {
style: {
width: `${this.naturalWidth}px`
}
});
}
return content;
},
__genPlaceholder() {
if (this.$slots.placeholder) {
const placeholder = this.isLoading ? [this.$createElement('div', {
staticClass: 'v-image__placeholder'
}, this.$slots.placeholder)] : [];
if (!this.transition) return placeholder[0];
return this.$createElement('transition', {
props: {
appear: true,
name: this.transition
}
}, placeholder);
}
}
},
render(h) {
const node = VResponsive.options.render.call(this, h);
const data = mergeData(node.data, {
staticClass: 'v-image',
attrs: {
'aria-label': this.alt,
role: this.alt ? 'img' : undefined
},
class: this.themeClasses,
// Only load intersect directive if it
// will work in the current browser.
directives: hasIntersect ? [{
name: 'intersect',
modifiers: {
once: true
},
value: {
handler: this.init,
options: this.options
}
}] : undefined
});
node.children = [this.__cachedSizer, this.__cachedImage, this.__genPlaceholder(), this.genContent()];
return h(node.tag, data, node.children);
}
});
//# sourceMappingURL=VImg.js.map