Skip to content

Commit

Permalink
feat: update @nuxt/image version and refactor NuxtPictureExt componen…
Browse files Browse the repository at this point in the history
…t and fixing hydration issues
  • Loading branch information
Simon Milfred committed Jan 11, 2024
1 parent ddf512c commit 17cbb69
Show file tree
Hide file tree
Showing 8 changed files with 3,077 additions and 1,986 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18.18
34 changes: 29 additions & 5 deletions .playground/app.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@

import NuxtPictureExt from '../components/NuxtPictureExt.vue';
<template>
<div>
<BaseImg
fit="cover"
width="400"
height="200"
sizes="<1080:100vw 1080px"
ratio="0.5"
class="w-auto h-[400px]"
src="/media/dj2bhlba/_hyt6524.jpg?rxy=0.5,0.5&width=2044&height=3072&rnd=133222323420230000"
@click="() => {}"
@click.native="() => {}"
ratio="3"
src="photo-1480714378408-67cf0d13bc1b.avif"
@click="console.log('click')"
@click.native="console.log('click.native')"
/>

<NuxtPictureExt
fit="cover"
width="400"
height="200"
src="photo-1507615000156-f066acbb8edc.avif"
/>

<NuxtPictureExt
fit="cover"
width="400"
height="200"
src="photo-1480714378408-67cf0d13bc1b.avif"
/>

<NuxtPicture
fit="cover"
width="400"
height="200"
src="photo-1480714378408-67cf0d13bc1b.avif"
loading="eager"
decoding="auto"
/>
</div>
</template>
Expand Down
Binary file not shown.
Binary file not shown.
222 changes: 115 additions & 107 deletions components/NuxtPictureExt.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<NuxtPicture
v-if="src"
ref="NuxtPicture"
v-if="src"
ref="nuxtPicture"
class="c-nuxt-picture-ext"
:class="`c-nuxt-picture-ext--${isLoaded ? 'is-loaded' : 'is-loading'}`"
:style="computedStyle"
Expand All @@ -20,129 +20,137 @@
:img-attrs="computedImgAttrs"
:quality="quality"
:loading="loading"
:decoding="decoding"
@load="onLoad"
/>
</template>

<script>
<script setup>
// https://image.nuxtjs.org/components/nuxt-picture and https://image.nuxtjs.org/components/nuxt-img
export default defineComponent({
name: 'NuxtPictureExt',
props: {
alt: {
type: String,
default: '',
},
src: {
type: String,
},
sizes: {
type: String,
default: null,
},
width: { type: [Number, String], required: false },
height: { type: [Number, String], required: false },
ratio: { type: [Number, String], required: false },
fit: { type: String, default: '' },
loading: {
type: String,
default: 'lazy',
validator: (value) => ['lazy', 'eager', 'auto'].includes(value),
},
imgAttrs: { type: Object, default: () => ({}) },
quality: { type: [Number, String], default: 100 },
modifiers: { type: Object, default: () => ({}) },
},
data() {
return {
isLoaded: false,
};
},
computed: {
urlParams() {
const obj = {};
if (this.src) {
const url = new URL(this.src, 'https://example.com');
const params = new URLSearchParams(url.search);
params.forEach((value, key) => {
obj[key] = value;
});
}
return obj;
},
computedStyle() {
let style = null;
// Aspect ratio
if (this.ratio) {
style = { aspectRatio: this.ratio };
} else if (this.width && this.height) {
style = { aspectRatio: `${this.width} / ${this.height}` };
} else if (this.urlParams.width && this.urlParams.height) {
style = { aspectRatio: `${this.urlParams.width} / ${this.urlParams.height}` };
}
// Focus point
if (this.fit && ['cover', 'none'].includes(this.fit) && this.urlParams.rxy?.split?.(',').length === 2) {
const [x, y] = this.urlParams.rxy.split(',');
style = {
...style,
'--object-position': `${Math.round(x * 10000) / 100}% ${Math.round(y * 10000) / 100}%`,
};
}
return style;
},
computedImgAttrs() {
const className = ['c-nuxt-picture-ext__img', this.imgAttrs.class];
const style = [this.imgAttrs.style];
if (this.fit && this.fit !== 'crop') {
style.unshift({ objectFit: this.fit });
}
return {
...this.imgAttrs,
class: className.filter(Boolean),
style: style.filter(Boolean),
};
},
},
mounted() {
const image = this.$el?.querySelector?.('img');
if (image) {
this.isLoaded = image.complete && image.naturalHeight !== 0;
}
const emit = defineEmits(['load']);
const props = defineProps({
alt: {
type: String,
default: '',
},
src: {
type: String,
},
sizes: {
type: String,
default: null,
},
width: { type: [Number, String] },
height: { type: [Number, String] },
ratio: { type: [Number, String] },
fit: { type: String, default: '' },
loading: {
type: String,
default: 'lazy',
validator: (value) => ['lazy', 'eager', 'auto'].includes(value),
},
decoding: {
type: String,
default: 'sync',
validator: (value) => ['async', 'sync', 'auto'].includes(value),
},
imgAttrs: { type: Object, default: () => ({}) },
quality: { type: [Number, String], default: 100 },
modifiers: { type: Object, default: () => ({}) },
});
const nuxtPicture = ref(null);
const isLoaded = ref(false);
const urlParams = computed(() => {
const obj = {};
if (props.src) {
const url = new URL(props.src, 'https://example.com');
const params = new URLSearchParams(url.search);
params.forEach((value, key) => {
obj[key] = value;
});
}
return obj;
});
methods: {
onLoad(e) {
this.$emit('load', e);
this.isLoaded = true;
},
},
const computedStyle = computed(() => {
let style = null;
// Aspect ratio
if (props.ratio) {
style = { aspectRatio: props.ratio };
} else if (props.width && props.height) {
style = { aspectRatio: `${props.width} / ${props.height}` };
} else if (urlParams.value.width && urlParams.value.height) {
style = { aspectRatio: `${urlParams.value.width} / ${urlParams.value.height}` };
}
// Focus point
if (props.fit && ['cover', 'none'].includes(props.fit) && urlParams.value.rxy?.split?.(',').length === 2) {
const [x, y] = urlParams.value.rxy.split(',');
style = {
...style,
'--object-position': `${Math.round(x * 10000) / 100}% ${Math.round(y * 10000) / 100}%`,
};
}
return style;
});
const computedImgAttrs = computed(() => {
const className = ['c-nuxt-picture-ext__img', props.imgAttrs.class];
const style = [props.imgAttrs.style];
if (props.fit && props.fit !== 'crop') {
style.unshift({ objectFit: props.fit });
}
return {
...JSON.parse(JSON.stringify(props.imgAttrs)),
class: className.filter(Boolean),
style: style.filter(Boolean),
};
return {};
});
onMounted(() => {
const image = nuxtPicture?.value?.$el?.querySelector?.('img');
image?.complete && onLoad();
});
defineExpose({
nuxtPicture,
isLoaded,
});
/* Methods */
function onLoad(e) {
e = e || new Event('load');
if (!isLoaded.value) {
emit('load', e);
isLoaded.value = true;
}
}
</script>
<style>
:where(.c-nuxt-picture-ext) {
display: inline-block;
width: auto;
height: auto;
}
:where(.c-nuxt-picture-ext__img) {
width: 100%;
height: 100%;
display: block;
min-width: 100%;
min-height: 100%;
max-width: 100%;
max-height: 100%;
object-position: var(--object-position);
}
</style>
3 changes: 1 addition & 2 deletions nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { createResolver } from "@nuxt/kit";
const { resolve } = createResolver(import.meta.url);

export default defineNuxtConfig({
modules: ["@nuxt/image-edge"],

modules: ["@nuxt/image"],
image: {
screens: {
// Remove the defaults
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@limbo-works/image",
"type": "module",
"version": "0.0.3",
"version": "1.0.0",
"main": "./nuxt.config.js",
"scripts": {
"dev": "nuxi prepare & nuxi dev .playground",
Expand All @@ -12,10 +12,10 @@
"test": "exit 0"
},
"dependencies": {
"@nuxt/image-edge": "1.0.0-rc.1-28139579.37395b7"
"@nuxt/image": "^1.1.0"
},
"devDependencies": {
"eslint": "^8.28.0",
"nuxt": "^3.0.0"
"eslint": "^8.56.0",
"nuxt": "^3.9.1"
}
}
Loading

0 comments on commit 17cbb69

Please sign in to comment.