import _clamp from "lodash/clamp"
import _get from "lodash/get"
import Vue from "vue"
export default {
amp: 'only',
ampLayout: 'default',
props: {
object: {
type: Object,
default: () => ({})
},
html: String,
src: String,
height: [String, Number],
width: [String, Number],
aspect: [String, Number],
videoSrc: [String, Boolean],
size: {
type: String,
default: "full"
},
color: {
type: String,
default: ""
},
respectMax: {
type: Boolean,
default: false
},
fillSpace: {
type: Boolean,
default: false
},
fit: {
type: String,
default: "cover"
},
poster: {
type: [String, Boolean],
default: ""
},
volume: {
type: Number,
default: 0
},
loop: {
type: Boolean,
default: true
},
progressHandler: {
type: Function,
required: false,
default: () => {}
},
useTransition: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
loading: true,
imageWidth: 0,
imageHeight: 0,
inView: false,
observerImgSettings: {
callback: this.setImgSrc,
intersection: {
rootMargin: "150px 0px 150px 0px",
threshold: 0
}
},
observerVideoSettings: {
callback: this.setVideoSrc,
intersection: {
rootMargin: "150px 150px 150px 150px",
threshold: 0
}
}
}
},
computed: {
aspectPadding() {
// default to defined aspect, or calculate
const calculatedAspect =
(this.parsedHeight / this.parsedWidth) * 100
return this.aspect || calculatedAspect || 56.25
},
classes() {
return [
"rsp-image-module",
"responsive-image",
`fit-${this.fit}`,
{ loading: this.loading },
{ "fill-space": this.fillSpace },
{ "has-video": this.parsedVideoSrc },
{ "in-view": this.inView }
]
},
imageTag() {
// TODO: Add other img attributes
if (this.$isAMP) {
console.log("AMP is working")
}else {
console.log("AMP is Not working")
}
const fallback = `<img data-umesh="Chandra" src="${this.parsedSrc}" alt="${this.parsedAlt}">`
if (this.html) return this.html
let html = fallback
// Make src and srcset data attributes
html = html.replace("src=", "data-src=")
html = html.replace("srcset=", "data-srcset=")
return html
},
innerHtml() {
return this.imageTag || ""
},
isAcf() {
// check to see if this is an ACF-serialized object
// search for the existence of keys that ACF objects have but Rest-Easy ones don't
return (
this.object.hasOwnProperty("filesize") &&
this.object.hasOwnProperty("mime_type") &&
this.object.hasOwnProperty("modified")
)
},
outerStyles() {
const styles = {}
// set max dims if needed
if (this["respect-max"]) {
styles["max-width"] = `${this.parsedWidth}px`
styles["max-height"] = `${this.parsedHeight}px`
}
// add color bg if needed
if (
this.parsedColor &&
this.parsedColor !== "transparent" &&
this.loading
) {
// set color or SVG background depending on settings
if (this.fit == "cover")
styles["background-color"] = this.parsedColor
else styles["background-image"] = `url("${this.svgBG}")`
// set fit
styles["background-size"] = this.fit
}
return styles
},
parsedAlt() {
return _get(this, "object.alt", "")
},
parsedColor() {
return (
this.color ||
_get(this.object, "primary_color") ||
"transparent"
)
},
parsedFocus() {
return _get(this, "object.focus", { x: 50, y: 50 })
},
parsedHeight() {
// default to defined height
if (this.height) return parseInt(this.height)
return this.imageHeight
},
parsedPoster() {
if (this.poster === null) return ""
return this.poster && this.poster.length
? this.poster
: this.parsedSrc
},
parsedSrc() {
// return hardcoded source if we have one
if (this.src) return this.src
// return ACF source if we have one (only respects fullscreen)
if (this.isAcf) {
return _get(this, "object.sizes.fullscreen", "")
}
return _get(this.targetSize, `url`)
},
parsedVideoSrc() {
const metaString =
_get(this.object, "meta.custom_video_url") ||
_get(this.object, "alt", "")
if (this.videoSrc || this.videoSrc === null) return this.videoSrc
else return String(metaString).includes(".mp4") ? metaString : ""
},
parsedWidth() {
// default to defined width
if (this.width) return parseInt(this.width)
return this.imageWidth
},
sizerStyles() {
if (!this.fillSpace) {
return {
paddingBottom: `${this.aspectPadding}%`
}
}
return {}
},
svgBG() {
if (!this.parsedColor || this.parsedColor == "transparent")
return ""
return `data:image/svg+xml;utf8,
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
x='0px' y='0px' viewBox='0 0 ${this.imageWidth} ${this.imageHeight}' xml:space='preserve'>
<rect fill='${this.parsedColor}' width='${this.imageWidth}' height='${this.imageHeight}' />
</svg>`.replace(/\r?\n|\r/g, "")
},
targetSize() {
// should return an object with { height, html, url, width }
// return ACF sizes
if (this.isAcf) {
return {
width: _get(this, "object.sizes.fullscreen-width", 0),
height: _get(this, "object.sizes.fullscreen-height", 0),
url: _get(this, "object.sizes.fullscreen", "")
}
}
// get sizes from image object
const sizes = _get(this.object, `sizes`, {})
// get specified size, or first available size
return (
_get(sizes, this.size) || sizes[_get(Object.keys(sizes), "[0]")]
)
},
transitionKey() {
if (this.useTransition) return this.parsedVideoSrc
else return false
}
},
watch: {
object() {
this.setObjectDimensions()
},
innerHtml() {
this.setMediaClass()
this.setFocalPoint()
},
volume() {
this.setVolume()
}
},
async mounted() {
// ignore if our src is undefined
if (!this.parsedSrc) return
// const img = new Image()
// img.src = this.parsedSrc
// // image was already in cache,
// // set loading var immediately
// if (img.complete) this.loading = false
// // set up height/width if we have an object
this.setObjectDimensions()
// wait for image to load...
this.setMediaClass()
this.setVolume()
// make sure the wrapped image is rendered
await Vue.nextTick()
// set focal point
this.setFocalPoint()
// Check if there's a video progress handler Function
// If there is pass it the current video details every 100ms
// If there is but there's no video, pass it false once
const video = this.$refs.video
if (this.progressHandler) {
if (video)
this.progressInterval = setInterval(() => {
this.progressHandler({
currentTime: video.currentTime,
duration: video.duration,
percentComplete: video.currentTime / video.duration
})
}, 100)
else this.progressHandler(false)
}
},
destroyed() {
if (this.progressInterval) clearInterval(this.progressInterval)
},
methods: {
setImgSrc(isVisible, entry) {
// Add in view class once
if (isVisible) {
this.inView = isVisible
}
if (this.$refs.imageWrap && isVisible && this.loading) {
let lazyImage = this.$refs.imageWrap.querySelector("img")
if (lazyImage.dataset.srcset) {
lazyImage.srcset = lazyImage.dataset.srcset
}
if (lazyImage.dataset.src) {
lazyImage.src = lazyImage.dataset.src
}
this.loading = false
}
},
setVideoSrc(isVisible, entry) {
// Add in view class once
if (isVisible) {
this.inView = isVisible
}
if (this.$refs.video && isVisible && this.loading) {
const lazyVideo = this.$refs.video
if (lazyVideo.dataset.poster) {
lazyVideo.poster = lazyVideo.dataset.poster
}
if (lazyVideo.dataset.src) {
lazyVideo.src = lazyVideo.dataset.src
}
this.loading = false
}
},
setFocalPoint() {
// find the wrapped image
if (
this.$refs.imageWrap &&
this.$refs.imageWrap.querySelector("*")
) {
const wrapped = this.$refs.imageWrap.querySelector("*")
// set its position (default: 50% 50%)
wrapped.style.objectPosition = `${this.parsedFocus.x}% ${this.parsedFocus.y}%`
}
},
setMediaClass() {
// give the "media" class to whatever we are rendering (img or video)
if (!this.$el || !this.$el.querySelector) return
Vue.nextTick(() => {
const media = this.$el.querySelector(".image-sizer > *")
if (media) media.classList.add("media")
})
},
setObjectDimensions() {
if (this.object) {
this.imageWidth = _get(this.targetSize, `width`)
this.imageHeight = _get(this.targetSize, `height`)
}
},
async setVolume() {
await Vue.nextTick()
const video = this.$el.querySelector("video")
if (video) {
video.volume = _clamp(this.volume, 0, 1)
}
}
}
}