-
-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image on demand service #658
Comments
This comment was marked as outdated.
This comment was marked as outdated.
I have released the version 1.1.1 With an adapted URL structure and a caching. |
Here is my temporary solution in the frontend:
<template>
<img
ref="image"
:src="src"
:width="width"
:height="height"
:class="[$style.image, { [$style.loaded]: loaded }]"
/>
</template>
<script lang="ts" setup>
import type { T3File } from '@t3headless/nuxt-typo3'
interface UtilImageOnDemand {
image?: T3File
type?: string
lazy?: boolean
dummy?: boolean
text?: string
bgColor?: string
textColor?: string
crop?: string
}
interface Querys {
id?: number
type?: string
text?: string
bgColor?: string
textColor?: string
crop?: string
}
const props = withDefaults(defineProps<UtilImageOnDemand>(), {
type: 'webp',
lazy: true,
dummy: false,
})
const { options } = useT3Options()
const baseUrl: string = `${options.api.baseUrl}/image-service`
const image = ref<HTMLElement>()
const loaded = ref<boolean>(false)
const observer = ref<IntersectionObserver>()
const src = ref<string>()
const name = ref<string>('dateiname.jpg')
const width = ref<number>(300)
const height = ref<number>(300)
const querys = reactive<Querys>({
type: props.type,
})
const queryString = computed<string>(() =>
Object.entries(querys)
.filter(([key, value]) => !!value)
.map(([key, value]) => `${key}=${encodeURIComponent(value as any)}`)
.join('&')
)
if (props.type) querys.type = props.type
if (props.crop) querys.crop = props.crop
if (props.image) {
const { fileReferenceUid: uid, filename, dimensions } = props.image.properties
querys.id = uid
name.value = filename
width.value = dimensions.width
height.value = dimensions.height
}
if (props.image && !props.lazy) {
src.value = `${baseUrl}/${width.value}/${height.value}/${name.value}?${queryString.value}`
}
if (props.image && props.lazy) {
const lazyWidth = 100
const lazyHeight = Math.round((height.value / width.value) * 100)
src.value = `${baseUrl}/${lazyWidth}/${lazyHeight}/${name.value}?${queryString.value}`
const onEnter = () => {
if (!image.value) return
image.value.addEventListener('load', () => {
loaded.value = true
observer.value?.disconnect()
})
const { offsetWidth, offsetHeight } = image.value
src.value = `${baseUrl}/${offsetWidth}/${offsetHeight}/${name.value}?${queryString.value}`
}
onMounted(() => {
observer.value = useIntersect(
image as Ref<HTMLElement>,
onEnter,
undefined,
undefined,
{
threshold: 0.4,
}
)
})
onUnmounted(() => {
observer.value?.disconnect()
})
}
if (!props.image && props.dummy) {
if (props.text) querys.text = props.text
if (props.bgColor) querys.bgColor = props.bgColor
if (props.textColor) querys.textColor = props.bgColor
src.value = `${baseUrl}/${width.value}/${height.value}?${queryString.value}`
loaded.value = true
}
</script>
<!-- TODO: Remove css from this utility component. Provide only functionality -->
<style lang="postcss" module>
.image {
@apply transition-all;
}
.image:not(.loaded) {
@apply blur-xl;
}
</style>
export const useIntersect = (
elementToWatch: Ref<HTMLElement> | HTMLElement,
callback: Function,
outCallback?: Function,
once: Boolean = true,
options = { threshold: 1.0 }
) => {
const observer = new IntersectionObserver(([entry]) => {
if (entry && entry.isIntersecting) {
callback(entry.target)
if (once) {
observer.unobserve(entry.target)
}
} else if (typeof outCallback === 'function') {
outCallback(entry.target)
}
}, options)
if (isRef(elementToWatch)) {
elementToWatch = elementToWatch.value
}
observer.observe(elementToWatch)
return observer
} |
@mercs600 please take a look at frontend solution, is this suitable for us? |
There are still a few issues & questions...
|
Another possibility could be to only provide the composable to generate the link. In the end, everyone can decide when and how to use the link. |
@mercs600; I have now written a composable function. Just let me know how you decide, looking forward to it, and hopefully you'll like it! interface ImageServiceOptions {
image?: T3File;
type?: Ref<string> | string; // webp
text?: Ref<string> | string; // Dummy Image
bgColor?: Ref<string> | string; // #fff
textColor?: Ref<string> | string; // #000
crop?: Ref<string> | string; // desktop
}
interface ImageServiceResizeProps {
width?: number;
height?: number;
crop?: string;
}
interface ImageServiceQuery {
id?: number;
type?: string;
text?: string;
bgColor?: string;
textColor?: string;
crop?: string;
}
export const useImageService = (options: ImageServiceOptions) => {
const { options: t3Options } = useT3Options();
const baseUrl: string = `${t3Options.api.baseUrl}/image-service`;
const width = ref<number>(300);
const height = ref<number>(300);
const name = ref<string>("dummy.jpg");
const query = reactive<ImageServiceQuery>({
type: "jpg",
});
const queryString = computed<string>(() =>
Object.entries(query)
.filter(([key, value]) => !!value)
.map(([key, value]) => `${key}=${encodeURIComponent(value as any)}`)
.join("&")
);
// watchEffect(() => {
if (options?.image) {
const { fileReferenceUid: uid, filename, dimensions } = options.image.properties // prettier-ignore
query.id = uid;
name.value = filename;
width.value = dimensions.width;
height.value = dimensions.height;
}
if (options?.type) query.type = toValue(options.type);
if (options?.text) query.text = toValue(options.text);
if (options?.bgColor) query.bgColor = toValue(options.bgColor);
if (options?.textColor) query.textColor = toValue(options.textColor);
if (options?.crop) query.crop = toValue(options.crop);
// })
const url = computed<string>(() => `${baseUrl}/${width.value}/${height.value}/${name.value}?${queryString.value}`) // prettier-ignore
const resize = (props: ImageServiceResizeProps): string => {
const { width: w, height: h, crop: c } = props;
if (w) width.value = w;
if (h) height.value = h;
if (c) query.crop = c;
// adjust height in proportion if only the width is specified and an image is present.
if (w && !h && options.image) {
const { dimensions } = options.image.properties;
width.value = w;
height.value = (dimensions.height / dimensions.width) * w;
}
// adjust width in proportion if only the height is specified and an image is present.
if (h && !w && options.image) {
const { dimensions } = options.image.properties;
width.value = h;
height.value = (dimensions.height / dimensions.width) * h;
}
return url.value;
};
return {
resize,
url,
};
}; |
@kubilaymelnikov usually we are handling image generation and resizing in imgproxy in nuxt-typo3 application, I think @mercs600 can give you more insight |
We have been developing exclusively headless websites for over 2 years now. However, I have always found dealing with image sizes on these sites to be quite cumbersome and insufficient. That's why I extended your
FilesProcessor
with breakpoints. After several optimizations, we achieved a rather streamlined result.This is how it looked in the end:
However, I was still not satisfied with this, as the configuration was still rather laborious. That's why I decided to develop an extension that allows you to load the image in the exact size you need.
Wouldn't something like this also be interesting for the Headless Core? After all, it's about making images available in the Headless mode.
I want to emphasize that this is not intended as self-promotion. I am not the best PHP developer and still need to work on the entire code, improve the structure, and so on.
Here is the link to my repository: https://github.com/wineworlds/image-on-demand-service/tree/develop
Furthermore, I plan to complete the Nuxt3 module and make it available in this repository via npm.
This new extension is already in use in our current web project, although the website is not yet officially online.
The text was updated successfully, but these errors were encountered: