I'm using an array of dummy data to test how I could display information from an api. In that data, I have a relative image path, for some images I've stored locally in $lib
for simplicity in testing.
I loop through each object in this dataset, and create a component with the relative image path passed to the object. In my testing the relative paths are resolving correctly, but I'm unable to load the images dynamically.
After doing a bit of digging, it seems that this is because of the file structure and permissions with $lib
. It seems that image import is easy to do if I use a static solution with the images, and just dynamically adjust the relative url, but I want to be able to use Vite's performance improvements when it comes to images. Indeed, Sveltekit documentation recommends storing images in a directory within $lib
for this exact reason.
In the component I can manually import each image from $lib
and it works as expected like so:
<script>
import img from '$lib/assets/sample-image.jpg';
</script>
<img src={img} />
But for the life of me I can't figure out how I can change the import path using a dynamic prop. I imagine it would look something like this:
import img from '$lib/assets/{object.imageUrlProp}';
Or some type of direct access in the image src
similar to how static usage would look like:
<img src="$lib/assets/{object.imageUrlProp}" alt="Image" />
But neither of these are valid syntax.
How can I dynamically import images stored in $lib
within a component in Svelte?
Brunnerh's answer works great for files with the same extension. As he explains in the comments, for files with differing extensions, a glob import is required. Here is the specific implementation solution I was able to get working. I had some trouble understanding how to access the modules structure normally passed back from the import, so using as: url
simplified that process for me. Here is the specific implementation solution I was able to get working:
<script lang="ts">
const images: any = import.meta.glob(['$lib/assets/**.jpg', '$lib/assets/**.png', '$lib/assets/**.svg'], { eager: true, as: 'url' });
<!--You can change allowed extensions here, or you could do something like '$lib/assets/**' without an extension to allow imports for any file types. The '**' allows for nested folders, so you can replace it with '*' if all assets are directly stored in the assets folder-->
<script>
<!--imageURL will look something like image1.png or folder1/image1.png-->
<img src={images["/src/lib/assets/" + imageURL} />
I was trying to iterate through JSON data containing relative image URLs:
<script lang="ts">
import { pages } from '$lib/data/sample_data.json';
const images: any = import.meta.glob(['$lib/images/**.jpg', '$lib/images/**.png', '$lib/images/**.svg'], { eager: true, as: 'url' });
</script>
{#each Object.entries(pages) as [key, page], index (key)}
<div class="page">
<img src={images["/src/lib/images/" + page.featured_image]} alt={page.image_alt} />
<h2>{page.title}</h2>
</div>
{/each}
<!--Sample JSON Data-->
{
"pages": [
{
"title": "First Page",
"featured_image": "image.png",
"image_alt": "alt for page 1"
},
{
"title": "Second Page",
"featured_image": "image2.jpg",
"image_alt": "alt for page 2"
},
{
"title": "Third Page",
"featured_image": "image3.svg",
"image_alt": "alt for page 3"
},
]
}
If you only provide the file name, you would need a dynamic import that has the name interpolated into it. You might have to keep the extension separate, so the bundler knows what is being imported.
{#await import(`$lib/assets/${object.imageUrlProp}.jpg`) then { default: src }}
<img {src} alt="Image" />
{/await}
You probably also could do a glob import (with eager
) in the parent, then you don't have to await
and can pass the full path that can be directly handed off to the image src
.
Should be along the lines of:
const images = import.meta.glob('$lib/assets/*.jpg', { eager: true });
<Component imgSrc={images[`$lib/assets/${object.imageName}.jpg`]} />
(The image path map could likely also be extracted to a separate module and then imported in the component. Have not tried that, though.)