Skip to content

Commit

Permalink
feat(QImg): new prop -> loading-show-delay #16932
Browse files Browse the repository at this point in the history
  • Loading branch information
rstoenescu committed Feb 29, 2024
1 parent 19a8e62 commit 380b003
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 39 deletions.
2 changes: 2 additions & 0 deletions ui/dev/src/pages/components/img.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
style="max-width: 400px"
:no-native-menu="noNativeMenu"
draggable
loading-show-delay="500"
>
<template v-slot:loading>
<div class="text-h2 text-white">
Expand All @@ -68,6 +69,7 @@
alt="Image"
style="max-width: 400px; border-radius: 50%"
:no-native-menu="noNativeMenu"
:loading-show-delay="500"
>
<div class="absolute-bottom text-subtitle1 text-center q-pa-xs">
Radius 50%
Expand Down
86 changes: 47 additions & 39 deletions ui/src/components/img/QImg.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { h, ref, computed, watch, onMounted, onBeforeUnmount, Transition } from 'vue'
import { h, ref, computed, watch, onMounted, Transition, getCurrentInstance } from 'vue'

import QSpinner from '../spinner/QSpinner.js'

import useRatio, { useRatioProps } from '../../composables/private/use-ratio.js'

import { createComponent } from '../../utils/private/create.js'
import { hSlot } from '../../utils/private/render.js'
import { vmIsDestroyed } from '../../utils/private/vm.js'
import useTimeout from '../../composables/private/use-timeout.js'

const defaultRatio = 16 / 9

Expand All @@ -30,6 +32,11 @@ export default createComponent({
type: String,
default: 'lazy'
},
loadingShowDelay: {
type: [ Number, String ],
default: 0
},

fetchpriority: {
type: String,
default: 'auto'
Expand Down Expand Up @@ -68,8 +75,10 @@ export default createComponent({
setup (props, { slots, emit }) {
const naturalRatio = ref(props.initialRatio)
const ratioStyle = useRatio(props, naturalRatio)
const vm = getCurrentInstance()

let loadTimer = null, isDestroyed = false
const { registerTimeout: registerLoadTimeout, removeTimeout: removeLoadTimeout } = useTimeout()
const { registerTimeout: registerLoadShowTimeout, removeTimeout: removeLoadShowTimeout } = useTimeout()

const images = [
ref(null),
Expand Down Expand Up @@ -103,6 +112,24 @@ export default createComponent({

watch(() => getCurrentSrc(), addImage)

function setLoading () {
removeLoadShowTimeout()

if (props.loadingShowDelay === 0) {
isLoading.value = true
return
}

registerLoadShowTimeout(() => {
isLoading.value = true
}, props.loadingShowDelay)
}

function clearLoading () {
removeLoadShowTimeout()
isLoading.value = false
}

function getCurrentSrc () {
return props.src || props.srcset || props.sizes
? {
Expand All @@ -120,74 +147,64 @@ export default createComponent({
}

function addImage (imgProps) {
if (loadTimer !== null) {
clearTimeout(loadTimer)
loadTimer = null
}

removeLoadTimeout()
hasError.value = false

if (imgProps === null) {
isLoading.value = false
clearLoading()
images[ position.value ^ 1 ].value = getPlaceholderSrc()
}
else {
isLoading.value = true
setLoading()
}

images[ position.value ].value = imgProps
}

function onLoad ({ target }) {
if (isDestroyed === true) { return }
if (vmIsDestroyed(vm) === false) {
removeLoadTimeout()

if (loadTimer !== null) {
clearTimeout(loadTimer)
loadTimer = null
}
naturalRatio.value = target.naturalHeight === 0
? 0.5
: target.naturalWidth / target.naturalHeight

naturalRatio.value = target.naturalHeight === 0
? 0.5
: target.naturalWidth / target.naturalHeight

waitForCompleteness(target, 1)
waitForCompleteness(target, 1)
}
}

function waitForCompleteness (target, count) {
// protect against running forever
if (isDestroyed === true || count === 1000) { return }
if (count === 1000 || vmIsDestroyed(vm) === true) { return }

if (target.complete === true) {
onReady(target)
}
else {
loadTimer = setTimeout(() => {
loadTimer = null
registerLoadTimeout(() => {
waitForCompleteness(target, count + 1)
}, 50)
}
}

function onReady (img) {
if (isDestroyed === true) { return }
if (vmIsDestroyed(vm) === true) { return }

position.value = position.value ^ 1
images[ position.value ].value = null
isLoading.value = false
clearLoading()
hasError.value = false
emit('load', img.currentSrc || img.src)
}

function onError (err) {
if (loadTimer !== null) {
clearTimeout(loadTimer)
loadTimer = null
}
removeLoadTimeout()
clearLoading()

isLoading.value = false
hasError.value = true
images[ position.value ].value = null
images[ position.value ^ 1 ].value = getPlaceholderSrc()

emit('error', err)
}

Expand Down Expand Up @@ -227,7 +244,7 @@ export default createComponent({
}

function getContent () {
if (isLoading.value !== true) {
if (isLoading.value === false) {
return h('div', {
key: 'content',
class: 'q-img__content absolute-full q-anchor--skip'
Expand Down Expand Up @@ -262,15 +279,6 @@ export default createComponent({
else {
addImage(getCurrentSrc())
}

onBeforeUnmount(() => {
isDestroyed = true

if (loadTimer !== null) {
clearTimeout(loadTimer)
loadTimer = null
}
})
}

return () => {
Expand Down
8 changes: 8 additions & 0 deletions ui/src/components/img/QImg.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@
"category": "behavior"
},

"loading-show-delay": {
"type": [ "Number", "String" ],
"desc": "Delay showing the spinner when image changes; Gives time for the browser to load the image from cache to prevent flashing the spinner unnecessarily; Value should represent milliseconds",
"default": 0,
"examples": [ "500", "caption-lines=\"1000\"" ],
"category": "behavior"
},

"crossorigin": {
"type": "String",
"desc": "Same syntax as <img> crossorigin attribute",
Expand Down

0 comments on commit 380b003

Please sign in to comment.