uikit
Version:
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
249 lines (181 loc) • 6.09 kB
JavaScript
import {createEvent, css, Dimensions, escape, getImage, includes, isUndefined, queryAll, startsWith, toFloat, toPx, trigger} from 'uikit-util';
export default {
args: 'dataSrc',
props: {
dataSrc: String,
dataSrcset: Boolean,
sizes: String,
width: Number,
height: Number,
offsetTop: String,
offsetLeft: String,
target: String
},
data: {
dataSrc: '',
dataSrcset: false,
sizes: false,
width: false,
height: false,
offsetTop: '50vh',
offsetLeft: '50vw',
target: false
},
computed: {
cacheKey({dataSrc}) {
return `${this.$name}.${dataSrc}`;
},
width({width, dataWidth}) {
return width || dataWidth;
},
height({height, dataHeight}) {
return height || dataHeight;
},
sizes({sizes, dataSizes}) {
return sizes || dataSizes;
},
isImg(_, $el) {
return isImg($el);
},
target: {
get({target}) {
return [this.$el, ...queryAll(target, this.$el)];
},
watch() {
this.observe();
}
},
offsetTop({offsetTop}) {
return toPx(offsetTop, 'height');
},
offsetLeft({offsetLeft}) {
return toPx(offsetLeft, 'width');
}
},
connected() {
if (!window.IntersectionObserver) {
setSrcAttrs(this.$el, this.dataSrc, this.dataSrcset, this.sizes);
return;
}
if (storage[this.cacheKey]) {
setSrcAttrs(this.$el, storage[this.cacheKey], this.dataSrcset, this.sizes);
} else if (this.isImg && this.width && this.height) {
setSrcAttrs(this.$el, getPlaceholderImage(this.width, this.height, this.sizes));
}
this.observer = new IntersectionObserver(this.load, {
rootMargin: `${this.offsetTop}px ${this.offsetLeft}px`
});
requestAnimationFrame(this.observe);
},
disconnected() {
this.observer && this.observer.disconnect();
},
update: {
read({image}) {
if (!this.observer) {
return false;
}
if (!image && document.readyState === 'complete') {
this.load(this.observer.takeRecords());
}
if (this.isImg) {
return false;
}
image && image.then(img => img && img.currentSrc !== '' && setSrcAttrs(this.$el, currentSrc(img)));
},
write(data) {
if (this.dataSrcset && window.devicePixelRatio !== 1) {
const bgSize = css(this.$el, 'backgroundSize');
if (bgSize.match(/^(auto\s?)+$/) || toFloat(bgSize) === data.bgSize) {
data.bgSize = getSourceSize(this.dataSrcset, this.sizes);
css(this.$el, 'backgroundSize', `${data.bgSize}px`);
}
}
},
events: ['resize']
},
methods: {
load(entries) {
// Old chromium based browsers (UC Browser) did not implement `isIntersecting`
if (!entries.some(entry => isUndefined(entry.isIntersecting) || entry.isIntersecting)) {
return;
}
this._data.image = getImage(this.dataSrc, this.dataSrcset, this.sizes).then(img => {
setSrcAttrs(this.$el, currentSrc(img), img.srcset, img.sizes);
storage[this.cacheKey] = currentSrc(img);
return img;
}, e => trigger(this.$el, new e.constructor(e.type, e)));
this.observer.disconnect();
},
observe() {
if (this._connected && !this._data.image) {
this.target.forEach(el => this.observer.observe(el));
}
}
}
};
function setSrcAttrs(el, src, srcset, sizes) {
if (isImg(el)) {
sizes && (el.sizes = sizes);
srcset && (el.srcset = srcset);
src && (el.src = src);
} else if (src) {
const change = !includes(el.style.backgroundImage, src);
if (change) {
css(el, 'backgroundImage', `url(${escape(src)})`);
trigger(el, createEvent('load', false));
}
}
}
function getPlaceholderImage(width, height, sizes) {
if (sizes) {
({width, height} = Dimensions.ratio({width, height}, 'width', toPx(sizesToPixel(sizes))));
}
return `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"></svg>`;
}
const sizesRe = /\s*(.*?)\s*(\w+|calc\(.*?\))\s*(?:,|$)/g;
function sizesToPixel(sizes) {
let matches;
sizesRe.lastIndex = 0;
while ((matches = sizesRe.exec(sizes))) {
if (!matches[1] || window.matchMedia(matches[1]).matches) {
matches = evaluateSize(matches[2]);
break;
}
}
return matches || '100vw';
}
const sizeRe = /\d+(?:\w+|%)/g;
const additionRe = /[+-]?(\d+)/g;
function evaluateSize(size) {
return startsWith(size, 'calc')
? size
.slice(5, -1)
.replace(sizeRe, size => toPx(size))
.replace(/ /g, '')
.match(additionRe)
.reduce((a, b) => a + +b, 0)
: size;
}
const srcSetRe = /\s+\d+w\s*(?:,|$)/g;
function getSourceSize(srcset, sizes) {
const srcSize = toPx(sizesToPixel(sizes));
const descriptors = (srcset.match(srcSetRe) || []).map(toFloat).sort((a, b) => a - b);
return descriptors.filter(size => size >= srcSize)[0] || descriptors.pop() || '';
}
function isImg(el) {
return el.tagName === 'IMG';
}
function currentSrc(el) {
return el.currentSrc || el.src;
}
const key = '__test__';
let storage;
// workaround for Safari's private browsing mode and accessing sessionStorage in Blink
try {
storage = window.sessionStorage || {};
storage[key] = 1;
delete storage[key];
} catch (e) {
storage = {};
}