mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Optimize media loading across app (#10592)
* v-image and "lazy load" working * fixed vars * all the other img uses * No longer require access token in url for files * Add lazy loading and size limits * Rename map-component source prop * Fix lint warning * Update app/src/views/public/public-view.vue Co-authored-by: ian <licitdev@gmail.com> * Fix lint * Fix missing file type icon * Fix null imageInfo error * Use video.js for media playback * Fix .js file display * Update package-lock.json * Update package-lock.json * update package.json * Update pnpm-lock.yaml * Remove unrelated addition on VDatePicker * Remove folder abstraction * Use image data based aspect ratio on preview * Base app rate throttle on API rate limit config * Configure app rate limit throttle based on api config * Convert v-image to script[setup] * Convert v-media to script[setup] * Cleanup v-media * Remove unneeded addTokenToUrl usages * Remove video.js It doesn't do authorization headers for mp4/mp3, so it's pointless Co-authored-by: rijkvanzanten <rijkvanzanten@me.com> Co-authored-by: ian <licitdev@gmail.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import VForm from './v-form';
|
||||
import VHover from './v-hover/';
|
||||
import VHighlight from './v-highlight.vue';
|
||||
import VIcon from './v-icon/';
|
||||
import VImage from './v-image.vue';
|
||||
import VIconFile from './v-icon-file.vue';
|
||||
import VInfo from './v-info/';
|
||||
import VInput from './v-input/';
|
||||
@@ -81,6 +82,7 @@ export function registerComponents(app: App): void {
|
||||
app.component('VHover', VHover);
|
||||
app.component('VHighlight', VHighlight);
|
||||
app.component('VIcon', VIcon);
|
||||
app.component('VImage', VImage);
|
||||
app.component('VIconFile', VIconFile);
|
||||
app.component('VInfo', VInfo);
|
||||
app.component('VInput', VInput);
|
||||
|
||||
87
app/src/components/v-image.vue
Normal file
87
app/src/components/v-image.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<img ref="imageElement" :src="inView ? srcData : emptyPixel" v-bind="attrsWithoutSrc" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onUnmounted, useAttrs } from 'vue';
|
||||
import { omit } from 'lodash';
|
||||
import api from '@/api';
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits(['error']);
|
||||
const attrs = useAttrs();
|
||||
|
||||
const imageElement = ref<HTMLImageElement>();
|
||||
|
||||
const inView = ref(false);
|
||||
|
||||
const emptyPixel =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||
const srcData = ref<string>(emptyPixel);
|
||||
|
||||
let loaded = false;
|
||||
|
||||
const observer = new IntersectionObserver(async (entries) => {
|
||||
if (entries.length === 0) return;
|
||||
inView.value = entries[0].isIntersecting;
|
||||
|
||||
if (entries[0].isIntersecting && !loaded && props.src) {
|
||||
try {
|
||||
const res = await api.get(props.src, {
|
||||
responseType: 'arraybuffer',
|
||||
params: {
|
||||
download: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.headers['content-type'].startsWith('image') === false) return;
|
||||
|
||||
const contentType = res.headers['content-type'];
|
||||
|
||||
const data = new Uint8Array(res.data);
|
||||
|
||||
// 5mb
|
||||
if (data.length > 1048576 * 5) {
|
||||
emit('error', new Error('Image too big to render'));
|
||||
return;
|
||||
}
|
||||
|
||||
let raw = '';
|
||||
|
||||
data.forEach((byte) => {
|
||||
raw += String.fromCharCode(byte);
|
||||
});
|
||||
|
||||
const base64 = btoa(raw);
|
||||
srcData.value = `data:${contentType};base64,${base64}`;
|
||||
} catch (err) {
|
||||
emit('error', err);
|
||||
} finally {
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (!imageElement.value) return;
|
||||
observer.observe(imageElement.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
observer.disconnect();
|
||||
});
|
||||
|
||||
const attrsWithoutSrc = computed(() => omit(attrs, ['src']));
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user