From 59dad45f6911244b9f54527f5d0652a674522aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Bell=C3=AAtre?= Date: Wed, 19 Oct 2022 16:34:33 +0200 Subject: [PATCH] Add support for densities --- playground/pages/responsive.vue | 2 ++ src/runtime/components/image.mixin.ts | 1 + src/runtime/components/nuxt-img.vue | 2 ++ src/runtime/components/nuxt-picture.vue | 1 + src/runtime/image.ts | 40 +++++++++++++++++++------ src/runtime/utils/index.ts | 7 +++++ src/types/image.ts | 1 + 7 files changed, 45 insertions(+), 9 deletions(-) diff --git a/playground/pages/responsive.vue b/playground/pages/responsive.vue index c2fbddac1..2c2f83b6e 100644 --- a/playground/pages/responsive.vue +++ b/playground/pages/responsive.vue @@ -17,12 +17,14 @@
diff --git a/src/runtime/components/image.mixin.ts b/src/runtime/components/image.mixin.ts index beb1b3fda..85aa2e1b3 100644 --- a/src/runtime/components/image.mixin.ts +++ b/src/runtime/components/image.mixin.ts @@ -21,6 +21,7 @@ export const imageMixin = defineMixin({ provider: { type: String, default: undefined }, sizes: { type: [Object, String] as unknown as () => string | Record, default: undefined }, + densities: { type: String, default: undefined }, preload: { type: Boolean, default: undefined }, // attributes diff --git a/src/runtime/components/nuxt-img.vue b/src/runtime/components/nuxt-img.vue index 5ee701762..4f7ca66cb 100644 --- a/src/runtime/components/nuxt-img.vue +++ b/src/runtime/components/nuxt-img.vue @@ -14,6 +14,7 @@ const defineComponent: DefineComponentWithMixin = (opts: any) => opts type NAttrs = typeof imageMixin['nImgAttrs'] & { sizes?: string srcset?: string + densities?: string } export default defineComponent({ @@ -54,6 +55,7 @@ export default defineComponent({ return this.$img.getSizes(this.src, { ...this.nOptions, sizes: this.sizes, + densities: this.densities, modifiers: { ...this.nModifiers, width: parseSize(this.width), diff --git a/src/runtime/components/nuxt-picture.vue b/src/runtime/components/nuxt-picture.vue index b4a821c4d..3ad6f0dd1 100644 --- a/src/runtime/components/nuxt-picture.vue +++ b/src/runtime/components/nuxt-picture.vue @@ -89,6 +89,7 @@ export default defineComponent({ const { srcset, sizes, src } = this.$img.getSizes(this.src, { ...this.nOptions, sizes: this.sizes || this.$img.options.screens, + densities: this.densities, modifiers: { ...this.nModifiers, format diff --git a/src/runtime/image.ts b/src/runtime/image.ts index e820ad7d5..b38d01d76 100644 --- a/src/runtime/image.ts +++ b/src/runtime/image.ts @@ -2,7 +2,7 @@ import defu from 'defu' import { hasProtocol, parseURL, joinURL, withLeadingSlash } from 'ufo' import type { ImageOptions, ImageSizesOptions, CreateImageOptions, ResolvedImage, MapToStatic, ImageCTX, $Img } from '../types/image' import { imageMeta } from './utils/meta' -import { parseSize } from './utils' +import { parseDensities, parseSize } from './utils' import { useStaticImageMap } from './utils/static-map' export function createImage (globalOptions: CreateImageOptions, nuxtContext: any) { @@ -161,8 +161,10 @@ function getPreset (ctx: ImageCTX, name?: string): ImageOptions { function getSizes (ctx: ImageCTX, input: string, opts: ImageSizesOptions) { const width = parseSize(opts.modifiers?.width) const height = parseSize(opts.modifiers?.height) + const densities = opts.densities ? parseDensities(opts.densities) : [1, 2] const hwRatio = (width && height) ? height / width : 0 - const variants = [] + const sizeVariants = [] + const srcVariants = [] const sizes: Record = {} @@ -195,25 +197,45 @@ function getSizes (ctx: ImageCTX, input: string, opts: ImageSizesOptions) { _cWidth = Math.round((_cWidth / 100) * screenMaxWidth) } const _cHeight = hwRatio ? Math.round(_cWidth * hwRatio) : height - variants.push({ + sizeVariants.push({ width: _cWidth, size, screenMaxWidth, media: `(max-width: ${screenMaxWidth}px)`, src: ctx.$img!(input, { ...opts.modifiers, width: _cWidth, height: _cHeight }, opts) }) + + if (densities) { + for (const density of densities) { + srcVariants.push({ + width: _cWidth * density, + src: ctx.$img!(input, { ...opts.modifiers, width: _cWidth * density, height: _cHeight ? _cHeight * density : undefined }, opts) + }) + } + } } - variants.sort((v1, v2) => v1.screenMaxWidth - v2.screenMaxWidth) + sizeVariants.sort((v1, v2) => v1.screenMaxWidth - v2.screenMaxWidth) + + // Remove duplicate size variants, + let previousSize = '' - const defaultVar = variants[variants.length - 1] - if (defaultVar) { - defaultVar.media = '' + // Loop in reverse order to allow safe deletion + for (let i = sizeVariants.length - 1; i >= 0; i--) { + const sizeVariant = sizeVariants[i] + if (sizeVariant.media === previousSize) { + sizeVariants.splice(i, 1) + } + previousSize = sizeVariant.size } + srcVariants.sort((v1, v2) => v1.width - v2.width) + + const defaultVar = srcVariants[srcVariants.length - 1] + return { - sizes: variants.map(v => `${v.media ? v.media + ' ' : ''}${v.size}`).join(', '), - srcset: variants.map(v => `${v.src} ${v.width}w`).join(', '), + sizes: sizeVariants.map(v => `${v.media ? v.media + ' ' : ''}${v.size}`).join(', '), + srcset: srcVariants.map(v => `${v.src} ${v.width}w`).join(', '), src: defaultVar?.src } } diff --git a/src/runtime/utils/index.ts b/src/runtime/utils/index.ts index e5e5cb0ef..0cfb6ecd7 100644 --- a/src/runtime/utils/index.ts +++ b/src/runtime/utils/index.ts @@ -91,3 +91,10 @@ export function parseSize (input: string | number | undefined = '') { } } } + +export function parseDensities (input: string | undefined = '') { + if (!input.length) { + return undefined + } + return input.split(' ').map(size => parseInt(size.replace('x', ''), 10)) +} diff --git a/src/types/image.ts b/src/types/image.ts index d0641a26f..36b080e81 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -9,6 +9,7 @@ export interface ImageModifiers { export interface ImageOptions { provider?: string, preset?: string, + densities?: string, modifiers?: Partial [key: string]: any }