Skip to content

Commit

Permalink
feat(picture): init nuxt-picture
Browse files Browse the repository at this point in the history
  • Loading branch information
farnabaz committed Sep 21, 2020
1 parent 992944e commit bce1645
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 131 deletions.
121 changes: 121 additions & 0 deletions src/runtime/components/nuxt-image-mixins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
export default {
props: {
src: {
type: [String, Object],
default: '',
required: true
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
legacy: {
type: Boolean,
default: false
},
alt: {
type: String,
default: ''
},
sets: {
type: [String, Array],
default: '',
},
format: {
type: String,
default: undefined
},
size: {
type: String,
default: 'cover'
},
operations: {
type: Object,
default: () => ({})
}
},
data() {
return {
srcset: [],
blurry: null,
loading: false,
loaded: false,
}
},
async fetch() {
if (!this.legacy) {
this.blurry = await this.$img.lqip(this.src)
}
},
mounted() {
if (!this.legacy) {
this.$img.$observer.add(this.$el, () => {
// OK, element is visible, Hoooray
this.loadOriginalImage()
})
}
},
computed: {
sizes() {
let sizes = this.sets;
if (typeof this.sets === 'string') {
sizes = this.sets
.split(',')
.map(set => set.match(/((\d+)\:)?(\d+)\s*(\((\w+)\))?/))
.filter(match => !!match)
.map((match) => ({
width: match[3],
breakpoint: match[2] ? `(min-width: ${match[2]}px)` : '',
format: match[5] || this.format
}))
}
if ((!Array.isArray(sizes) || !sizes.length)) {
sizes = [{
media: '',
width: this.width ? parseInt(this.width, 10) : undefined,
height: this.height ? parseInt(this.height, 10) : undefined,
format: this.format
}]
}
sizes = sizes.map(size => ({
...size,
url: this.generateSizedImage(size.width, size.height, size.format)
}))

return sizes;
}
},
watch: {
async src(v) {
this.blurry = await this.$img.lqip(this.src)
this.original = null
this.$img.$observer.remove(this.$el)
this.$img.$observer.add(this.$el, () => {
// OK, element is visible, Hoooray
this.loadOriginalImage()
})
}
},
methods: {
generateSizedImage(width: number, height: number, format: string) {
const image = this.$img(this.src, {
width,
height,
format,
size: this.size,
...this.operations
})
return encodeURI(image)
},
loadOriginalImage() {
this.loading = true
}
},
beforeDestroy () {
this.$img.$observer.remove(this.$el)
},
}
15 changes: 8 additions & 7 deletions src/runtime/components/nuxt-image.css
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
.__nuxt-image {
.__nim_w {
position: relative;
overflow: hidden;
display: inline-block;
}
.__nuxt-image img {
.__nim_w img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
transition: opacity 800ms ease 0ms;
object-fit: cover;
}
.__nuxt-image-rel.full {
.__nim_full {
width: 100%;
height: 100%;
}
.__nuxt-image-rel.blur {
.__nim_blur {
filter: blur(25px);
transform: scale(1.1);
opacity: 1;
}

.__nuxt-image-abs,
.__nuxt-image.visible .__nuxt-image-rel.blur {
.__nim_org,
.__nim_w.visible .__nim_blur {
opacity: 0;
}
.__nuxt-image.visible .__nuxt-image-abs {
.__nim_w.visible .__nim_org {
opacity: 1;
}
142 changes: 18 additions & 124 deletions src/runtime/components/nuxt-image.ts
Original file line number Diff line number Diff line change
@@ -1,132 +1,41 @@
import nuxtImageMixin from './nuxt-image-mixins'

import './nuxt-image.css'

const imageHTML = ({ generatedSrc, generatedSrcset, generatedSizes, width, height, alt }) =>
`<img class="__nim_org" src="${generatedSrc}" srcset="${generatedSrcset}" sizes="${generatedSizes}" width="${width}" height="${height}" alt="${alt}" >`

export default {
name: "NuxtImage",
props: {
src: {
type: [String, Object],
default: '',
required: true
},
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
},
legacy: {
type: Boolean,
default: false
},
alt: {
type: String,
default: ''
},
sets: {
type: [String, Array],
default: '',
},
format: {
type: String,
default: undefined
},
size: {
type: String,
default: 'cover'
},
operations: {
type: Object,
default: () => ({})
}
},
data() {
return {
srcset: [],
blurry: null,
loading: false,
loaded: false,
}
},
async fetch() {
if (!this.legacy) {
this.blurry = await this.$img.lqip(this.src)
}
},
mounted() {
this.$img.$observer.add(this.$el, () => {
// OK, element is visible, Hoooray
this.loadOriginalImage()
})
},
mixins: [nuxtImageMixin],
computed: {
sizes() {
let sizes = this.sets;
if (typeof this.sets === 'string') {
sizes = this.sets.split(',').filter(Boolean).map((set) => {
let [breakpoint, width] = set.split(':').map(num => parseInt(num.trim(), 10))
return width ? {
breakpoint: `(min-width:${breakpoint}px) `,
width: width,
} : {
breakpoint: '',
width: breakpoint
}
})
}
if ((!Array.isArray(sizes) || !sizes.length)) {
sizes = [{
breakpoint: '',
width: this.width ? parseInt(this.width, 10) : undefined,
height: this.height ? parseInt(this.height, 10) : undefined,
}]
}
sizes = sizes.map(size => ({ ...size, url: this.generateSizedImage(size.width, size.height) }))

return sizes;
generatedSrcset() {
return this.sizes.map(({ width, url }) => width ? `${url} ${width}w` : url).join(', ')
},
generatedSizes() {
return this.sizes.map(({ width, media }) => width ? `${media} ${width}` : media).reverse().join(', ')
},
generatedSrc() {
if (this.sizes.length) {
return this.sizes[0].url
}
return this.src
},
generatedSrcset() {
return this.sizes.map(({ width, url }) => width ? `${url} ${width}w` : url).join(', ')
},
generatedSizes() {
return this.sizes.map(({ width, breakpoint }) => width ? `${breakpoint}${width}` : breakpoint).reverse().join(', ')
}
},
beforeDestroy () {
this.$img.$observer.remove(this.$el)
},
watch: {
async src(v) {
this.blurry = await this.$img.lqip(this.src)
this.original = null
this.$img.$observer.remove(this.$el)
this.$img.$observer.add(this.$el, () => {
// OK, element is visible, Hoooray
this.loadOriginalImage()
})
}
},
render(h) {
if (this.legacy) {
return this.renderNonOptimizedImage(h)
return this.renderLegacy(h)
}
const bluryImage = h('img', {
class: '__nuxt-image-rel full blur',
class: '__nim_full __nim_blur',
attrs: {
src: this.blurry,
alt: this.alt
}
})

const originalImage = h('img', {
class: ['__nuxt-image-abs'],
class: ['__nim_org'],
attrs: {
src: this.loading ? this.generatedSrc : undefined,
srcset: this.loading ? this.generatedSrcset : undefined,
Expand All @@ -144,13 +53,11 @@ export default {


const noScript = h('noscript', {
domProps: {
innerHTML: `<img class="__nuxt-image-abs visible" src="${this.generatedSrc}" srcset="${this.generatedSrcset}" sizes="${this.generatedSizes}" width="${this.width}" height="${this.height}" alt="${this.alt}" >`
}
domProps: { innerHTML: imageHTML(this) }
}, [])

const placeholder = h('div', {
class: '___nuxt-image-pl',
class: '__nim_pl',
style: {
paddingBottom: this.height ? `${this.height}` : undefined,
}
Expand All @@ -160,13 +67,13 @@ export default {
style: {
width: this.width ? this.width : undefined
},
class: ['__nuxt-image', this.loaded ? 'visible' : ''],
class: ['__nim_w', this.loaded ? 'visible' : ''],
}, [bluryImage, originalImage, noScript, placeholder])

return wrapper;
},
methods: {
renderNonOptimizedImage(h) {
renderLegacy(h) {
return h('img', {
class: '',
attrs: {
Expand All @@ -176,19 +83,6 @@ export default {
alt: this.alt
}
})
},
generateSizedImage(width: number, height: number) {
const image = this.$img(this.src, {
width,
height,
format: this.format,
size: this.size,
...this.operations
})
return encodeURI(image)
},
loadOriginalImage() {
this.loading = true
}
}
}
Loading

0 comments on commit bce1645

Please sign in to comment.