Skip to content

Commit

Permalink
Merge pull request #460 from RyanCoulsonCA/fix-388
Browse files Browse the repository at this point in the history
fix broken background image transitions in editor preview
  • Loading branch information
yileifeng authored Sep 10, 2024
2 parents 0d2d140 + aefe1b0 commit cce46bf
Showing 1 changed file with 76 additions and 23 deletions.
99 changes: 76 additions & 23 deletions src/components/story/background-image.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<div class="sticky z-10 grid-background" style="top: 60px; height: 100vh">
<!-- Vue3 transition for switching between a slide with no background a slide with a background. -->
<Transition name="fade" mode="out-in">
<div v-if="newImage !== 'none'" class="w-full h-full">
<img v-if="oldImage !== 'none'" class="fade-in w-full h-full" :src="oldImage" />
<div v-if="state.newImage !== 'none'" class="w-full h-full">
<img v-if="state.oldImage !== 'none'" class="fade-in w-full h-full" :src="state.oldImage" />
<img
class="fade-in w-full h-full"
:class="{ hide: activeImage === 1 }"
:src="newImage"
:src="state.newImage"
alt="Background image"
/>
</div>
Expand All @@ -16,19 +16,33 @@
</div>
</template>

<script setup>
import { watch, ref } from 'vue';
<script setup lang="ts">
import { watch, reactive, ref, PropType } from 'vue';
import { ConfigFileStructure } from '@storylines/definitions';
const emit = defineEmits(['background-changed']);
const props = defineProps({
src: {
type: String,
required: true
},
configFileStructure: {
type: Object as PropType<ConfigFileStructure>
}
});
const oldImage = ref('none');
const newImage = ref('none');
interface BlobStore {
[key: string]: string;
}
// Acts as a cache for images that we've already converted to a blob.
const blobStore: BlobStore = {};
const state = reactive({
oldImage: 'none',
newImage: 'none'
});
const activeImage = ref(0);
watch(
Expand All @@ -43,28 +57,67 @@ watch(
// us the effect of the new background smoothly coming in. Once the opacity hits 0, we move the new background image
// to the primary element and set the opacity back to 1.
oldImage.value = props.src;
activeImage.value = 1;
getImageSource(props.src).then((newImage) => {
state.oldImage = newImage;
activeImage.value = 1;
// This is the crossfade case where we're switching between two background images.
if (props.src !== 'none' && newImage.value !== 'none') {
setTimeout(() => {
// This is the crossfade case where we're switching between two background images.
if (props.src !== 'none' && state.newImage !== 'none') {
setTimeout(() => {
activeImage.value = 0;
state.oldImage = state.newImage;
state.newImage = newImage;
emit('background-changed', true);
}, 350); // timeout length is set to animation time (0.3s) plus a little bit of buffer.
} else {
// Not a crossfade case. We're either transitioning from nothing into an image or from an image into nothing.
// This case uses Vue3 transitions.
state.newImage = newImage;
activeImage.value = 0;
oldImage.value = newImage.value;
newImage.value = props.src;
emit('background-changed', true);
}, 350); // timeout length is set to animation time (0.3s) plus a little bit of buffer.
} else {
// Not a crossfade case. We're either transitioning from nothing into an image or from an image into nothing.
// This case uses Vue3 transitions.
newImage.value = props.src;
activeImage.value = 0;
emit('background-changed', newImage.value !== 'none');
}
emit('background-changed', state.newImage !== 'none');
}
});
},
{ immediate: false }
);
/**
* In order to display background images in the editor preview mode. Creates a BLOB URL for each background URL the first time
* we encounter it. In a normal Storyline, this promise will immediately resolve.
* @param src the original image source
*/
const getImageSource = (src: string): Promise<string> => {
return new Promise((resolve) => {
if (props.configFileStructure) {
// If this source has already been converted to a blob, return it.
if (blobStore[src] !== undefined) {
resolve(blobStore[src]);
return;
}
const assetSrc = `${src.substring(src.indexOf('/') + 1)}`;
const imageFile = props.configFileStructure?.zip.file(assetSrc);
if (imageFile) {
// Convert the image to a blob so it can be displayed locally.
imageFile.async('blob').then((res: Blob) => {
const blob = URL.createObjectURL(res);
// Assign to blobStore and return the result.
blobStore[src] = blob;
resolve(blobStore[src]);
return;
});
} else {
resolve(src);
return;
}
} else {
resolve(src);
return;
}
});
};
</script>

<style scoped>
Expand Down

0 comments on commit cce46bf

Please sign in to comment.