Skip to content

Commit

Permalink
feat: $img.getSources and better srcset for <nuxt-picture>
Browse files Browse the repository at this point in the history
Co-Authored-By: Sebastien Chopin <seb@nuxtjs.com>
  • Loading branch information
pi0 and atinux committed Jan 22, 2021
1 parent 43ba95f commit 36e039b
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 56 deletions.
4 changes: 2 additions & 2 deletions playground/pages/picture.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
</select>
<!-- <h2>SVG image inside project</h2>
<NuxtPicture :loading="true" src="/images/nuxt-white.svg" width="40" height="40" /> -->
<NuxtPicture placeholder src="/images/nuxt-white.svg" width="40" height="40" />
<!-- <NuxtPicture placeholder src="/images/nuxt-white.svg" width="40" height="40" /> -->

<h2>JPEG image inside project</h2>
<NuxtPicture placeholder width="600" height="300" :src="src" />
<NuxtPicture placeholder :src="src" />
<!-- <div style="height: 2000px;" /> -->
<h2>JPEG image from remote url</h2>
<!-- <NuxtPicture placeholder width="600" height="331" src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Aconcagua2016.jpg/600px-Aconcagua2016.jpg" /> -->
Expand Down
6 changes: 2 additions & 4 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ async function imageModule (moduleOptions: ModuleOptions) {
const options: ModuleOptions = defu(moduleOptions, nuxt.options.image, defaults)
// Sanitize sizes
if (!Array.isArray(options.sizes)) {
options.sizes = [320, 420, 768, 1024, 1200, 1600]
} else {
// Sort sizes from lowest to highest
options.sizes.sort((s1, s2) => s1 - s2)
// https://screensiz.es/
options.sizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
}

options.provider = process.env.NUXT_IMAGE_PROVIDER || options.provider || 'static'
Expand Down
84 changes: 34 additions & 50 deletions src/runtime/components/nuxt-picture.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
:style="{ opacity: isLoaded ? 0 : 1 }"
>
<picture v-if="isVisible">
<source v-for="(source, index) of sources" :key="index" v-bind="source">
<source v-if="sources[1]" v-bind="sources[1]">
<img
v-if="isVisible"
class="img"
decoding="async"
alt="nAlt"
:alt="nAlt"
:referrerpolicy="referrerpolicy"
:usemap="usemap"
:longdesc="longdesc"
:ismap="ismap"
:crossorigin="crossorigin"
:src="legacySource.srcset[0].split(' ')[0]"
:srcset="legacySource.srcset"
:src="defaultSrc"
:srcset="sources[0].srcset"
:style="{ opacity: isLoaded ? 1 : 0 }"
:loading="isLazy ? 'lazy' : 'eager'"
@load="onImageLoaded"
Expand Down Expand Up @@ -69,7 +69,8 @@ export default {
provider: { type: String, required: false, default: undefined },
// extras
placeholder: { type: [Boolean, String], default: false }
placeholder: { type: [Boolean, String], default: false },
sizes: { type: [Array], default: undefined }
},
data () {
const isLazy = this.loading === 'lazy'
Expand All @@ -80,7 +81,7 @@ export default {
},
computed: {
ratio () {
return parseSize(this.height) / parseSize(this.width)
return this.nHeight / this.nWidth
},
isVisible () {
if (this.lazyState === LazyState.IDLE) {
Expand All @@ -94,6 +95,12 @@ export default {
nAlt () {
return this.alt ?? generateAlt(this.src)
},
nWidth () {
return parseSize(this.width)
},
nHeight () {
return parseSize(this.height)
},
isTransparent () {
return ['png', 'webp', 'gif'].includes(this.originalFormat)
},
Expand Down Expand Up @@ -123,52 +130,11 @@ export default {
fit: this.fit
}
},
legacySource () {
return this.sources[this.sources.length - 1]
defaultSrc () {
return this.sources[0].srcset[0].split(' ')[0]
},
sources () {
if (this.nFormat === 'svg') {
return [{
srcset: this.src
}]
}
const breakpoints = this.$img.options.sizes
const densities = [1, 2]
const formats = this.nLegacyFormat !== this.nFormat ? [this.nFormat, this.nLegacyFormat] : [this.nFormat]
const variants = []
for (const format of formats) {
for (const width of breakpoints) {
variants.push({
width,
height: this.ratio ? Math.round(width * this.ratio) : parseSize(this.height),
media: `(max-width: ${width}px)`,
format
})
}
}
const sources = variants.map((variant) => {
return {
media: variant.media,
type: `image/${variant.format}`,
srcset: densities.map((density) => {
const { url } = this.$img(this.src, {
modifiers: {
...this.modifiers,
width: variant.width * density,
height: variant.height ? variant.height * density : undefined,
format: variant.format
}
})
return `${url} ${density}x`
})
}
})
return sources
return this.getSources()
},
srcset () {
if (this.nFormat === 'svg') {
Expand Down Expand Up @@ -197,6 +163,12 @@ export default {
return this.ratio ? `${this.ratio * 100}%` : '100%'
}
},
created () {
if (process.server && process.static) {
// Force compute sources into ssrContext
this.getSources()
}
},
mounted () {
if (this.isLazy) {
this.observe()
Expand All @@ -206,6 +178,18 @@ export default {
this.unobserve()
},
methods: {
getSources () {
if (this.nFormat === 'svg') {
return [{
srcset: this.src
}]
}
return this.$img.getSources(this.src, {
formats: this.nLegacyFormat !== this.nFormat ? [this.nLegacyFormat, this.nFormat] : [this.nFormat],
modifiers: this.modifiers,
sizes: this.sizes
})
},
observe () {
this._removeObserver = useObserver(this.$el, type => this.onObservered(type))
},
Expand Down
43 changes: 43 additions & 0 deletions src/runtime/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export function createImage (globalOptions: CreateImageOptions, nuxtContext) {
}

$img.getMeta = (input: string, options: ImageOptions) => getMeta(ctx, input, options)
// eslint-disable-next-line no-use-before-define
$img.getSources = (input: string, options: GetSourcesOptions) => getSources(ctx, input, options)

return $img
}
Expand Down Expand Up @@ -124,3 +126,44 @@ function getPreset (ctx: ImageCTX, name?: string): ImageOptions {
}
return ctx.options.presets[name]
}

interface GetSourcesOptions {
formats?: string[]
sizes?: string[]
modifiers?: any,
width?: number,
height?: number,
}

function getSources (ctx: ImageCTX, input: string, opts: GetSourcesOptions) {
let widths = [].concat(opts.sizes || ctx.options.sizes)
if (opts.width) {
widths.push(opts.width)
widths.push(opts.width * 2)
widths = widths.filter(w => w <= opts.width)
}
widths = Array.from(new Set(widths))
.sort((s1, s2) => s1 - s2) // unique & lowest to highest

const sizes = []
for (const width of widths) {
sizes.push({
width,
height: (opts.height / opts.width) || opts.height
})
}

const getURL = (width, height, format) => ctx.$img(input, {
modifiers: { ...opts.modifiers, width, height, format }
}).url

const sources = (opts.formats || [undefined]).map((format) => {
return {
type: format ? `image/${format}` : undefined,
sizes: sizes.map(({ width }) => `(max-width: ${width}px) ${width}px`),
srcset: sizes.map(({ width, height }) => `${getURL(width, height, format)} ${width}w`)
}
})

return sources
}
1 change: 1 addition & 0 deletions src/types/image.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface CreateImageOptions {
provider: string
intersectOptions: object
responsiveSizes: number[]
sizes: string[]
allow: AllowlistOptions
}

Expand Down

0 comments on commit 36e039b

Please sign in to comment.