daichendt.one/src/lib/components/Image.svelte
2022-10-22 22:56:10 +02:00

168 lines
5.9 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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
> images 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>