@ecomplus/storefront-components
Version:
Vue components for E-Com Plus Storefront
195 lines (184 loc) • 5.1 kB
JavaScript
import {
$ecomConfig,
img as getImg
} from '@ecomplus/utils'
import lozad from 'lozad'
const getBestFitThumb = (picture, containerWidth, containerHeight, containerBreakpoints) => {
let bestFitThumb, bestFitBreakpoint
for (const thumb in containerBreakpoints) {
const thumbBreakpoint = containerBreakpoints[thumb]
if (thumbBreakpoint !== undefined && picture[thumb]) {
if (bestFitBreakpoint !== undefined) {
if (thumbBreakpoint === null) {
if (bestFitBreakpoint >= containerWidth) {
continue
}
} else if (
thumbBreakpoint < containerWidth ||
thumbBreakpoint - 50 <= containerHeight ||
(bestFitBreakpoint !== null && thumbBreakpoint > bestFitBreakpoint)
) {
continue
}
}
bestFitThumb = thumb
bestFitBreakpoint = thumbBreakpoint
}
}
return bestFitThumb
}
export default {
name: 'APicture',
props: {
src: [String, Object],
fallbackSrc: String,
alt: String,
canCalcHeight: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: '/assets/img-placeholder.png'
},
containerBreakpoints: {
type: Object,
default () {
return {
zoom: null,
big: 800,
[$ecomConfig.get('default_img_size') || 'normal']: 400
}
}
},
lozadOptions: {
type: Object,
default () {
return {
rootMargin: '350px 0px',
threshold: 0
}
}
}
},
data () {
return {
sources: [],
imgWidth: 0,
imgHeight: 0,
height: null,
opacity: null
}
},
computed: {
defaultImgObj () {
if (typeof this.src === 'object' && this.src) {
return getImg(this.src) || this.src
}
return {}
},
localFallbackSrc () {
const { src, defaultImgObj, fallbackSrc } = this
if (fallbackSrc) {
return fallbackSrc
}
const fixedSrc = typeof src === 'object'
? src.zoom
? src.zoom.url
: defaultImgObj.url
: src
return fixedSrc ? fixedSrc.replace(/\.webp$/, '') : this.placeholder
},
localAlt () {
const { alt, src, defaultImgObj } = this
if (alt) {
return alt
} else if (!src) {
return 'No image'
}
return defaultImgObj.alt || 'Product'
}
},
methods: {
updateSources () {
const sources = []
let srcset
if (typeof this.src === 'object') {
const { clientWidth, clientHeight } = this.$el
const thumb = getBestFitThumb(this.src, clientWidth, clientHeight, this.containerBreakpoints)
const imgObj = this.src[thumb]
const { url, size } = (imgObj || this.defaultImgObj)
srcset = url
if (size) {
[this.imgWidth, this.imgHeight] = size.split('x').map(px => parseInt(px, 10))
if (clientWidth && this.imgHeight && this.canCalcHeight) {
this.height = (clientWidth >= this.imgWidth
? this.imgHeight
: clientWidth * this.imgHeight / this.imgWidth) +
'px'
}
}
} else {
srcset = this.src
}
if (srcset) {
if (srcset.endsWith('.webp')) {
sources.push({
srcset,
type: 'image/webp'
}, {
srcset: /\/imgs\/[0-9]{3}px/.test(srcset)
? srcset.replace(/\/imgs\/[0-9]{3}px/, '')
: srcset.replace(/\.webp$/, ''),
type: `image/${(srcset.substr(-9, 4) === '.png' ? 'png' : 'jpeg')}`
})
} else if (srcset.endsWith('.avif')) {
sources.push({
srcset,
type: 'image/avif'
}, {
srcset: srcset.replace('.avif', '.webp'),
type: 'image/webp'
})
} else {
sources.push({ srcset })
}
}
this.sources = sources
}
},
mounted () {
this.updateSources()
this.$nextTick(() => {
const $picture = this.$el
const observer = lozad($picture, {
...this.lozadOptions,
loaded: $el => {
const { localFallbackSrc } = this
const $img = $el.tagName === 'IMG' ? $el : $el.lastChild
$img.style.opacity = 0
if (this.imgHeight) {
$img.height = this.imgHeight
$img.width = this.imgWidth
}
$img.onerror = function () {
console.error(new Error('Image load error'), this)
$el.style.display = 'none'
const $newImg = document.createElement('IMG')
$newImg.src = localFallbackSrc
$el.parentNode.insertBefore($newImg, $el.nextSibling)
}
$img.onload = () => {
this.opacity = 0
$el.classList.add('loaded')
this.$nextTick(() => {
this.opacity = $img.style.opacity = null
this.$emit('load')
})
}
}
})
observer.observe()
})
}
}