Skip to content

Commit

Permalink
feat!: screen based responsive (#182)
Browse files Browse the repository at this point in the history
Co-authored-by: Sebastien Chopin <[email protected]>
  • Loading branch information
pi0 and atinux authored Feb 15, 2021
1 parent a58440b commit 51e2e0a
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 113 deletions.
4 changes: 2 additions & 2 deletions docs/content/en/components/nuxt-picture.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ position: 202

- Loading Placeholder
- Serve modern format `webp` when browser supports it
- Generates responsive `srcSet`
- Generates responsive srcset

If you want to use modern and optimized formats like `webp` or `avif` and support browsers like `IE` or `Safari` you should use the `nuxt-picture` component.
If you want to use modern and optimized formats like `webp` or `avif` and support browsers like `IE` or `Safari` you should use `nuxt-picture` component.

The `nuxt-picture` component is based on the HTML `<picture>` tag. This component is designed to support modern formats and improve browser compatibility at the same time.

Expand Down
5 changes: 5 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import type { } from '../src/types'
export default <NuxtConfig> {
components: true,
target: 'static',
head: {
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
},
modules: [
'../src/module.ts'
],
Expand Down
77 changes: 63 additions & 14 deletions playground/pages/responsive.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,70 @@
<template>
<div class="container">
<h2>Local SVG image</h2>
<NuxtImg layout="responsive" src="images/nuxt-white.svg" />
<!-- <h2>nuxt-img</h2>
<NuxtImg
:sizes="{ sm: 100, md: 500 }"
src="/images/damavand.jpg"
/>
<h2>Local JPEG image</h2>
<NuxtImg layout="responsive" src="/images/damavand.jpg" />
<h2>nuxt-picture</h2>
<NuxtPicture
:sizes="{ sm: 100, md: 500 }"
src="/images/damavand.jpg"
/> -->
<div style="width: 50vw">
i'm 50vw
</div>
<nuxt-img
src="/logos/nuxt.png"
sizes="sm:100vw md:50vw lg:400px"
/>
<img src="/_ipx/logos/nuxt.png?w=384" alt="nuxt" sizes="(max-width: 640px) 640px, 384px" srcset="/_ipx/logos/nuxt.png?w=640 640w, /_ipx/logos/nuxt.png?w=384 384w" data-v-7d203d15="">
<br>
<nuxt-picture
src="/logos/nuxt.png"
sizes="sm:100vw md:50vw lg:400px"
/>
<br>
<nuxt-img
src="/logos/nuxt.png"
:sizes="{ sm: '100vw', md: '50vw', lg: 400 }"
/>
<br>
<img
src="/logos/nuxt.png"
sizes="(max-width: 640px) 100vw, (max-width: 768px) 50vw, 400px"
:srcset="`${l(320)} 320w,${l(640)} 640w,${l(384)} 384w,${l(400)} 400w`"
>
</div>
</template>

<h2>JPEG image from remote url</h2>
<NuxtImg layout="responsive" src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Aconcagua2016.jpg/600px-Aconcagua2016.jpg" />
<script>
export default {
methods: {
l (txt) {
return 'https://textoverimage.moesif.com/image?image_url=https%3A%2F%2Fraw.githubusercontent.com%2Fnuxt%2Fnuxt.js%2Fdev%2F.github%2Fnuxt.png&text=' + txt
}
}
}
</script>

<h2>PNG image on Cloudinary</h2>
<NuxtImg layout="responsive" provider="cloudinary" src="/remote/nuxt-org/blog/going-full-static/main" />
<style scoped>
@media (max-width: 640px) {
img2 {
max-width: 100vw;
width: 100vw;
}
}
<h2>JPEG image on TwicPics</h2>
<NuxtImg layout="responsive" provider="twicpics" src="/football.jpg" />
@media (max-width: 768px) {
img2 {
max-width: 50vw;
width: 50vw;
}
}
<h2>JPEG image on Fastly</h2>
<NuxtImg layout="responsive" provider="fastly" src="/image.jpg" />
</div>
</template>
img2 {
max-width: 300px;
width: 300px;
}
</style>
Binary file added playground/static/logos/nuxt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 11 additions & 7 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,28 @@ async function imageModule (moduleOptions: ModuleOptions) {
dir: resolve(nuxt.options.srcDir, nuxt.options.dir.static),
domains: [],
sharp: {},
sizes: undefined,
// https://tailwindcss.com/docs/breakpoints
screens: {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
xxl: 1536,
'2xl': 1536
},
internalUrl: '',
providers: {},
static: {},
intersectOptions: {}
}

const options: ModuleOptions = defu(moduleOptions, nuxt.options.image, defaults)
// Sanitize sizes
if (!Array.isArray(options.sizes)) {
// https://screensiz.es/
options.sizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
}

options.provider = process.env.NUXT_IMAGE_PROVIDER || options.provider || 'static'

const imageOptions: Omit<CreateImageOptions, 'providers'> = pick(options, [
'sizes',
'screens',
'presets',
'provider',
'intersectOptions'
Expand Down
45 changes: 25 additions & 20 deletions src/runtime/components/nuxt-img.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default {
// options
preset: { type: String, required: false, default: undefined },
provider: { type: String, required: false, default: undefined },
responsive: { type: Boolean, required: false, default: false }
sizes: { type: [Object, String], required: false, default: undefined }
},
data () {
return {
Expand All @@ -51,17 +51,33 @@ export default {
if (this.usePlaceholder) {
return EMPTY_GIF
}
if (this.sizes) {
return this.nSizes.src
}
return this.$img(this.src, this.nModifiers, this.nOptions)
},
nAttrs () {
const attrs: any = {}
if (this.responsive) {
const { sizes, srcSet } = this.getResponsive()
if (this.sizes) {
const { sizes, srcset } = this.nSizes
attrs.sizes = sizes
attrs.srcSet = srcSet
attrs.srcset = srcset
} else if (this.sizes) {
attrs.sizes = this.sizes
}
return attrs
},
nSizes () {
return this.$img.getSizes(this.src, {
...this.nOptions,
sizes: this.sizes,
modifiers: {
...this.nModifiers,
width: parseSize(this.width),
height: parseSize(this.height)
}
})
},
nModifiers () {
return {
...this.modifiers,
Expand All @@ -82,8 +98,11 @@ export default {
},
created () {
if (process.server && process.static) {
// Force compute sources into ssrContext
this.getResponsive()
if (this.sizes) {
// Force compute sources into ssrContext
// eslint-disable-next-line no-unused-expressions
this.nSizes
}
}
},
mounted () {
Expand All @@ -95,20 +114,6 @@ export default {
this.unobserve()
},
methods: {
getResponsive () {
const sizes = this.$img.getSizes(this.src, {
...this.nOptions,
modifiers: {
...this.nModifiers,
width: parseSize(this.width),
height: parseSize(this.height)
}
}, this.sizes)
return {
sizes: sizes.map(({ width }) => `(max-width: ${width}px) ${width}px`),
srcSet: sizes.map(({ width, src }) => `${src} ${width}w`)
}
},
observe () {
this._removeObserver = useObserver(this.$el, () => {
this.usePlaceholder = false
Expand Down
87 changes: 45 additions & 42 deletions src/runtime/components/nuxt-picture.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
:style="{ opacity: isLoaded ? 0 : 1 }"
>
<picture v-if="isVisible">
<source v-if="sources[1]" v-bind="sources[1]">
<source
v-if="nSources[1]"
:key="nSources[1].src"
:type="nSources[1].type"
:srcset="nSources[1].srcset"
:sizes="nSources[1].sizes"
>
<img
v-if="isVisible"
class="img"
Expand All @@ -20,10 +26,10 @@
:longdesc="longdesc"
:ismap="ismap"
:crossorigin="crossorigin"
:src="defaultSrc"
:srcset="sources[0].srcset"
:src="nSources[0].src"
:srcset="nSources[0].srcset"
:sizes="nSources[0].sizes"
:style="{ opacity: isLoaded ? 1 : 0.01 }"
:sizes="sources[0].sizes"
:loading="isLazy ? 'lazy' : 'eager'"
@load="onImageLoaded"
@onbeforeprint="onPrint"
Expand Down Expand Up @@ -72,7 +78,7 @@ export default {
// extras
placeholder: { type: [Boolean, String], default: false },
sizes: { type: [Array], default: undefined }
sizes: { type: [Object, String], required: false, default: undefined }
},
data () {
const isLazy = this.loading === 'lazy'
Expand Down Expand Up @@ -139,11 +145,38 @@ export default {
preset: this.preset
}
},
defaultSrc () {
return this.sources[0].srcset[0].split(' ')[0]
},
sources () {
return this.getSources()
nSources () {
if (this.nFormat === 'svg') {
return [{
srcset: this.src
}]
}
const formats = this.nLegacyFormat !== this.nFormat
? [this.nLegacyFormat, this.nFormat]
: [this.nFormat]
const sources = formats.map((format) => {
const { srcset, sizes, src } = this.$img.getSizes(this.src, {
...this.nOptions,
sizes: this.sizes || this.$img.options.screens,
modifiers: {
...this.nModifiers,
width: this.nWidth,
height: this.nHeight,
format
}
})
return {
src,
type: `image/${format}`,
sizes,
srcset
}
})
return sources
},
srcset () {
if (this.nFormat === 'svg') {
Expand Down Expand Up @@ -173,7 +206,8 @@ export default {
created () {
if (process.server && process.static) {
// Force compute sources into ssrContext
this.getSources()
// eslint-disable-next-line no-unused-expressions
this.nSources
}
},
mounted () {
Expand All @@ -185,37 +219,6 @@ export default {
this.unobserve()
},
methods: {
getSources () {
if (this.nFormat === 'svg') {
return [{
srcset: this.src
}]
}
const formats = this.nLegacyFormat !== this.nFormat
? [this.nLegacyFormat, this.nFormat]
: [this.nFormat]
const sources = formats.map((format) => {
const sizes = this.$img.getSizes(this.src, {
...this.nOptions,
modifiers: {
...this.nModifiers,
width: this.nWidth,
height: this.nHeight,
format
}
}, this.sizes)
return {
type: `image/${format}`,
sizes: sizes.map(({ width }) => `(max-width: ${width}px) ${width}px`),
srcset: sizes.map(({ width, src }) => `${src} ${width}w`)
}
})
return sources
},
observe () {
this._removeObserver = useObserver(this.$el, type => this.onObservered(type))
},
Expand Down
Loading

0 comments on commit 51e2e0a

Please sign in to comment.