168 lines
5.9 KiB
Svelte
168 lines
5.9 KiB
Svelte
<script lang="ts">
|
||
/**
|
||
* the output of a vite-imagetools import, using the `meta` query for output
|
||
* format
|
||
*
|
||
* full type:
|
||
* [code](https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/output-formats.ts),
|
||
* [docs](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/guide/getting-started.md#metadata)
|
||
*/
|
||
export let meta: { src: string; width: number; format: string }[];
|
||
// if there is only one, vite-imagetools won't wrap the object in an array
|
||
if (!(meta instanceof Array)) meta = [meta];
|
||
|
||
// all images by format
|
||
let sources = new Map<string, typeof meta>();
|
||
meta.map((m) => sources.set(m.format, []));
|
||
meta.map((m) => (sources.get(m.format) ?? []).push(m));
|
||
|
||
// fallback image: first resolution of last format
|
||
let image = (sources.get([...sources.keys()].slice(-1)[0]) ?? [])[0];
|
||
|
||
/**
|
||
* `source` attribute. default: width of the first resolution specified in the
|
||
* import.
|
||
*/
|
||
export let sizes = '100vw';
|
||
|
||
/** `img` attribute */
|
||
export let alt: string;
|
||
|
||
/** `img` attribute */
|
||
export let loading: string = 'lazy';
|
||
</script>
|
||
|
||
<!--
|
||
@component
|
||
takes the output of a vite-imagetools import (using the `meta` output format)
|
||
and generates a `<picture>` with `<source>` tags and an `<img>`.
|
||
|
||
usage
|
||
|
||
- in `global.d.ts`
|
||
```typescript
|
||
declare module "*&imagetools" {
|
||
const out;
|
||
export default out;
|
||
}
|
||
```
|
||
- in svelte file
|
||
- typescript
|
||
```typescript
|
||
import Image from "$lib/Image.svelte";
|
||
import me from "$lib/assets/me.jpg?w=200;400&format=webp;png&meta&imagetools";
|
||
```
|
||
- html
|
||
```html
|
||
<span><Image meta="{me}" alt="me" /></span>
|
||
```
|
||
- it's not necessary to wrap it in a `<span>`, but i like to avoid unnested
|
||
`:global()` selectors in svelte css
|
||
- scss
|
||
```scss
|
||
span :global(img) {
|
||
border-radius: 50%;
|
||
}
|
||
```
|
||
|
||
example generated `<picture>`
|
||
|
||
```html
|
||
<picture>
|
||
<source
|
||
sizes="200px"
|
||
type="image/webp"
|
||
srcset="
|
||
/_app/assets/me-3cfc7c5f.webp 200w,
|
||
/_app/assets/me-ab564f98.webp 400w
|
||
"
|
||
/>
|
||
<source
|
||
sizes="200px"
|
||
type="image/png"
|
||
srcset="
|
||
/_app/assets/me-2bc09a6d.png 200w,
|
||
/_app/assets/me-6f16cc18.png 400w
|
||
"
|
||
/>
|
||
<img src="/_app/assets/me-2bc09a6d.png" alt="me" />
|
||
</picture>
|
||
```
|
||
|
||
notes
|
||
|
||
- from the documentation for
|
||
[`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes),
|
||
|
||
> The selected source size affects the intrinsic size of the image (the
|
||
> image’s display size if no CSS styling is applied). If the srcset
|
||
> attribute is absent, or contains no values with a width descriptor, then
|
||
> the sizes attribute has no effect.
|
||
|
||
there are other things that may also affect the intrinsic (and separately,
|
||
display) size of the image, but this is all we set here.
|
||
|
||
- the `&imagetools` in the usage above is to make typescript happy. there are
|
||
other workarounds, if you'd prefer a differnet one
|
||
https://github.com/JonasKruckenberg/imagetools/issues/160
|
||
- it'd be nice if we could just use a plain `<img>` tag, but in my bit of
|
||
testing that didn't seem to allow for multiple formats. i was also tempted
|
||
to just use png, but in my bit of testing the webp file was only ~10% (!)
|
||
the size of the png.
|
||
|
||
assumptions
|
||
|
||
- this counts on vite-imagetools returning metadata objects in the same order
|
||
as the query values are specified
|
||
- e.g. for `?width=100;200&format=webp;png&meta` we expect the source with
|
||
`width=100` to come before the one with `width=200`, and likewise for
|
||
`webp` and `png`
|
||
- i don't think this is guaranteed, so hopefully it doesn't change. looks
|
||
like it depends on this bit of code
|
||
https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/lib/resolve-configs.ts#L17
|
||
|
||
references
|
||
|
||
- responsive images
|
||
- mdn https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
|
||
- css-tricks https://css-tricks.com/a-guide-to-the-responsive-images-syntax-in-html/
|
||
- web
|
||
- html
|
||
- picture https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
|
||
- source https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source
|
||
- img https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img
|
||
- js
|
||
- Map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||
- ts
|
||
- wildcard module declarations https://www.typescriptlang.org/docs/handbook/modules.html#wildcard-module-declarations
|
||
- docs
|
||
- vite-imagetools https://github.com/JonasKruckenberg/imagetools/tree/main/docs
|
||
- other
|
||
- how to generate srcset https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/output-formats.ts
|
||
- `Map` preserves insertion order https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||
- you don't set elements on `Map` objects the way you do on regular objects
|
||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
|
||
(this was really hard to figure out lol)
|
||
- vite-imagetools extensions (to make the import query string shorter)
|
||
- docs https://github.com/JonasKruckenberg/imagetools/blob/main/docs/guide/extending.md
|
||
- code (options) https://github.com/JonasKruckenberg/imagetools/blob/main/packages/vite/src/types.ts
|
||
-->
|
||
<picture>
|
||
{#each [...sources.entries()] as [format, meta]}
|
||
<source
|
||
{sizes}
|
||
type="image/{format}"
|
||
srcset={meta.map((m) => `${m.src} ${m.width}w`).join(', ')}
|
||
/>
|
||
{/each}
|
||
<img src={image.src} {alt} {loading} />
|
||
</picture>
|
||
|
||
<style>
|
||
img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
</style>
|