Skip to content

Instantly share code, notes, and snippets.

@SebiBasti
Created October 28, 2025 13:35
Show Gist options
  • Select an option

  • Save SebiBasti/a5c3c7378059507ab7c8d3fa28a7e942 to your computer and use it in GitHub Desktop.

Select an option

Save SebiBasti/a5c3c7378059507ab7c8d3fa28a7e942 to your computer and use it in GitHub Desktop.
Optimized CloudImage Component
<script setup lang="ts">
import { Cloudinary } from '@cloudinary/url-gen'
import { sharpen } from '@cloudinary/url-gen/actions/adjust'
import { dpr, format, quality } from '@cloudinary/url-gen/actions/delivery'
import { limitFit } from '@cloudinary/url-gen/actions/resize'
import { auto as fAuto } from '@cloudinary/url-gen/qualifiers/format'
import { auto as qAuto } from '@cloudinary/url-gen/qualifiers/quality'
import { AdvancedImage, lazyload } from '@cloudinary/vue'
import { useDebounce, useResizeObserver } from '@vueuse/core'
import { nextTick, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue'
/**
* Component: CloudImage
*
* Purpose:
* Renders an adaptive Cloudinary image that automatically requests
* the correct width and device pixel ratio (DPR) based on the element’s
* rendered size.
*
* Implementation details:
* - Uses a ResizeObserver to track the <img> element width.
* - Debounces width updates to avoid spamming Cloudinary requests.
* - Dynamically adjusts DPR for sharpness and efficiency.
* - Applies auto format (f_auto) and quality (q_auto) transformations.
*
* Notes:
* We intentionally omit Cloudinary’s `responsive()` plugin to avoid
* overlapping ResizeObservers. This component fully owns its own
* responsiveness.
*/
/**
* Props
* - imageName: Public ID of the image in Cloudinary.
* - alt: Alternative text for accessibility.
*/
const props = defineProps<{
imageName: string
alt: string
}>()
const cloud = new Cloudinary({
cloud: { cloudName: import.meta.env.VITE_CLOUDINARY_NAME }
})
/**
* Layout and DPR thresholds
* - BASE_MIN_WIDTH: Prevents very small width requests during layout shifts.
* - MAX_W: Caps the requested width to avoid massive derivatives.
* - DPR_SWITCH_AT: Switches from DPR=2 to DPR=auto beyond this width.
*/
const BASE_MIN_WIDTH = 320
const MAX_W = 3440
const DPR_SWITCH_AT = 1400
/**
* Element + Resize tracking
* - imageRef: Reference to the <AdvancedImage> component (its $el is the <img> element).
* - rawWidth: Immediate width reported by ResizeObserver.
* - debouncedWidth: Smoothed width value used for transformations.
*/
const imageRef = ref()
const rawWidth = ref(0)
const debouncedWidth = useDebounce(rawWidth, 100, { maxWait: 200 })
/**
* Handle to stop ResizeObserver when the component unmounts.
* VueUse’s `useResizeObserver()` returns an object with a `.stop()` method.
*/
let stopResizeObserver: (() => void) | undefined
/**
* Reactive Cloudinary image instance.
* Updated whenever debounced width or DPR conditions change.
*/
const cldImage = ref(cloud.image(props.imageName))
/**
* onMounted()
* Wait for DOM render → locate <img> element → start ResizeObserver.
* Seeds an initial width measurement for the first transformation.
*/
onMounted(async () => {
await nextTick()
const imgEl: HTMLElement | undefined = imageRef.value?.$el
if (!imgEl) {
return
}
/**
* Initial measurement (rounded to nearest pixel)
*/
rawWidth.value = Math.round(imgEl.getBoundingClientRect().width || 0)
/**
* Start observing the <img> element for size changes
*/
const { stop } = useResizeObserver(imgEl, (entries) => {
const entry = entries[0]
rawWidth.value = Math.round(entry.contentRect.width || 0)
})
stopResizeObserver = stop
})
/**
* Disconnect the ResizeObserver to prevent memory leaks.
*/
onBeforeUnmount(() => {
stopResizeObserver?.()
})
/**
* Reactive transformation logic
*
* Recomputes the Cloudinary URL whenever the debounced width changes.
* Skips rebuilding the URL if effective parameters (width, DPR, sharpen)
* are identical to the previous transformation.
*/
type DprMode = '2' | 'auto'
let lastApplied = { width: 0, dpr: '2' as DprMode, sharpened: false }
let lastUrl = ''
watchEffect(() => {
/**
* Fallback width for SSR / early render
*/
const w = debouncedWidth.value || 800
/**
* Clamp the measured width to safe limits
*/
const safeWidth = Math.min(MAX_W, Math.max(BASE_MIN_WIDTH, w))
/**
* Determine DPR strategy
*/
const dprMode: DprMode = w >= DPR_SWITCH_AT ? 'auto' : '2'
/**
* Apply gentle sharpening for small images
*/
const shouldSharpen = w < 600
/**
* Skip rebuild if nothing effectively changed
*/
if (
lastApplied.width === safeWidth &&
lastApplied.dpr === dprMode &&
lastApplied.sharpened === shouldSharpen
) {
return
}
/**
* Construct base Cloudinary transformation
*/
const base = cloud
.image(props.imageName)
.delivery(format(fAuto()))
.delivery(quality(qAuto()))
.resize(limitFit().width(safeWidth))
/**
* Apply DPR mode
*/
const withDpr = dprMode === 'auto' ? base.delivery(dpr('auto')) : base.delivery(dpr(2))
/**
* Optionally apply sharpening
*/
if (shouldSharpen) withDpr.adjust(sharpen())
/**
* Assign only if the resulting URL is new
*/
const url = withDpr.toURL()
if (url !== lastUrl) {
cldImage.value = withDpr
lastUrl = url
lastApplied = { width: safeWidth, dpr: dprMode, sharpened: shouldSharpen }
}
})
</script>
<template>
<AdvancedImage
ref="imageRef"
:cldImg="cldImage"
:alt="alt || imageName"
:plugins="[lazyload()]"
/>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment