Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Svelte 5 #51

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
824 changes: 547 additions & 277 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zerodevx/svelte-img",
"version": "2.1.0",
"version": "3.0.0",
"description": "High-performance responsive/progressive images for SvelteKit",
"author": "Jason Lee <[email protected]>",
"scripts": {
Expand All @@ -19,7 +19,7 @@
"vite-imagetools": "5.0.8"
},
"peerDependencies": {
"svelte": "^3.55.1 || ^4.0.0"
"svelte": "^5.0.0"
},
"devDependencies": {
"@fontsource-variable/inter": "^5.0.8",
Expand All @@ -29,6 +29,7 @@
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.25.0",
"@sveltejs/package": "^2.2.2",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.16",
"daisyui": "^3.7.7",
Expand All @@ -38,14 +39,14 @@
"postcss": "^8.4.30",
"postcss-load-config": "^4.0.1",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"prettier-plugin-svelte": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.5.4",
"svelte": "^4.2.1",
"svelte-check": "^3.5.2",
"svelte": "^5.0.0-next.127",
"svelte-check": "^3.7.1",
"tailwindcss": "^3.3.3",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^4.4.9"
"vite": "^5.2.11"
},
"type": "module",
"exports": {
Expand Down
75 changes: 46 additions & 29 deletions src/lib/FxParallax.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,40 @@ import Img from './SvelteImg.svelte'
import { observe } from './utils.js'
import { onMount } from 'svelte'

let classes = ''
export { classes as class }
/**
* @type {number} number between 0 to 1 to control speed relative to scroll
* @callback onclick
* @param {MouseEvent & { currentTarget: EventTarget & HTMLImageElement }} event
*/

/**
* @callback onload
* @param {Event & { currentTarget: EventTarget & Element }} event
*/

/**
* @typedef FxParallaxProps
* @property {string} class
* @property {number} factor number between 0 to 1 to control speed relative to scroll
* - value closer to 0 is faster, while a value closer to 1 is slower
* - value of 1 behaves normally
* - value of 0 effectively makes the element scroll fixed with the page
* @property {HTMLImageElement | undefined} ref bindable reference to <img> element
* @property {onclick} onclick
* @property {onload} onload
*/
export let factor = 0.75
/** @type {HTMLImageElement|undefined} bindable reference to <img> element */
export let ref = undefined

let mounted = false
let inview = false
let scrollY = 0
let offsetHeight = 0
let screenHeight = 0
let stamp = 0
let height = 100
let offset = 0
let normalized = 0
/** @type {FxParallaxProps} */
let { factor = 0.75, ref = $bindable(), onload, onclick, ...rest } = $props()

let mounted = $state(false)
let inview = $state(false)
let scrollY = $state(0)
let offsetHeight = $state(0)
let screenHeight = $state(0)
let stamp = $state(0)
let height = $state(100)
let offset = $state(0)
let normalized = $state(0)

function entered(e) {
stamp = scrollY + e.detail.boundingClientRect.top
Expand All @@ -34,38 +47,42 @@ function resized() {
screenHeight = window.screen.height
}

$: normalized = Math.abs(factor - 1)
$effect(() => (normalized = Math.abs(factor - 1)))

$: if (screenHeight && offsetHeight) {
height = 100 + normalized * (screenHeight / offsetHeight) * 100
}
$effect(() => {
if (screenHeight && offsetHeight) {
height = 100 + normalized * (screenHeight / offsetHeight) * 100
}
})

$: if (inview) {
offset = Math.floor((scrollY - stamp) * normalized)
}
$effect(() => {
if (inview) {
offset = Math.floor((scrollY - stamp) * normalized)
}
})

onMount(() => {
resized()
mounted = true
})
</script>

<svelte:window bind:scrollY on:resize={resized} />
<svelte:window bind:scrollY onresize={resized} />

<div
class="wrap {classes}"
class="wrap {rest.class}"
class:mounted
bind:offsetHeight
use:observe
on:enter={entered}
on:leave={() => (inview = false)}
onenter={entered}
onleave={() => (inview = false)}
>
<Img
style="height:{height}%;transform:translate(0,{offset}px)"
bind:ref
on:load
on:click
{...$$restProps}
{onload}
{onclick}
{...rest}
/>
</div>

Expand Down
60 changes: 39 additions & 21 deletions src/lib/FxReveal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,44 @@ import Img from './SvelteImg.svelte'
import { observe, len, lqipToBackground } from './utils.js'
import { onMount } from 'svelte'

/** @type {Object} imagetools import */
export let src = {}
/** @type {HTMLImageElement|undefined} bindable reference to <img> element */
export let ref = undefined
/**
* @callback onclick
* @param {MouseEvent & { currentTarget: EventTarget & HTMLImageElement }} event
*/

let meta = {}
let background
let mounted = false
let loaded = false
let inview = false
/**
* @callback onload
* @param {Event & { currentTarget: EventTarget & Element }} event
*/

$: if (len(src)) {
loaded = false
const { lqip, src: s, w, h } = src.img
background = lqip ? lqipToBackground(lqip) : undefined
const { sources = {} } = src
meta = { img: { src: s, w, h }, sources }
} else {
meta = {}
}
/**
* @typedef FxRevealProps
* @property {Object} src imagetools import
* @property {HTMLImageElement | undefined} ref bindable reference to <img> element
* @property {onclick} onclick
* @property {onload} onload
*/

/** @type {FxRevealProps} */
let { src, ref = $bindable(), onload = () => (loaded = true), onclick, ...rest } = $props()

let meta = $state({})
let background = $state(undefined)
let mounted = $state(false)
let loaded = $state(false)
let inview = $state(false)

$effect(() => {
if (len(src)) {
loaded = false
const { lqip, src: s, w, h } = src.img
background = lqip ? lqipToBackground(lqip) : undefined
const { sources = {} } = src
meta = { img: { src: s, w, h }, sources }
} else {
meta = {}
}
})

onMount(() => {
mounted = true
Expand All @@ -36,10 +54,10 @@ onMount(() => {
class:mounted
class:reveal={loaded && inview}
use:observe
on:enter={() => (inview = true)}
onenter={() => (inview = true)}
>
<Img src={meta} bind:ref on:load on:load={() => (loaded = true)} on:click {...$$restProps} />
<div class="lqip" style:background />
<Img src={meta} bind:ref {onload} {onclick} {...rest} />
<div class="lqip" style:background></div>
</div>
{/if}

Expand Down
40 changes: 24 additions & 16 deletions src/lib/Picture.svelte
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
<script>
import { len } from './utils.js'

export let sources = {}
export let sizes = undefined
/**
* @typedef PictureProps
* @property {Object} sources
* @property {string | undefined} sizes
*/

let srcs = []
/** @type {PictureProps} */
let { sources = {}, sizes, children } = $props()

$: if (len(sources)) {
const list = []
for (const [format, imgs] of Object.entries(sources)) {
list.push({
format,
srcset: imgs.map((i) => `${i.src} ${i.w}w`).join()
})
let srcs = $state([])

$effect(() => {
if (len(sources)) {
const list = []
for (const [format, imgs] of Object.entries(sources)) {
list.push({
format,
srcset: imgs.map((i) => `${i.src} ${i.w}w`).join()
})
}
srcs = list
} else {
srcs = []
}
srcs = list
} else {
srcs = []
}
})
</script>

{#if len(srcs)}
<picture>
{#each srcs as { format, srcset }}
<source type="image/{format}" {sizes} {srcset} />
{/each}
<slot />
{@render children()}
</picture>
{:else}
<slot />
{@render children()}
{/if}
71 changes: 44 additions & 27 deletions src/lib/SvelteImg.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,63 @@
import Picture from './Picture.svelte'
import { len, lqipToBackground } from './utils.js'

/** @type {Object} imagetools import */
export let src = {}
/** @type {string|undefined} img tag `sizes` attr */
export let sizes = undefined
/** @type {number|undefined} img width override */
export let width = undefined
/** @type {number|undefined} img height override */
export let height = undefined
/** @type {'lazy'|'eager'} img tag `loading` attr */
export let loading = 'lazy'
/** @type {'async'|'auto'|'sync'} img tag `decoding` attr */
export let decoding = 'async'
/** @type {HTMLImageElement|undefined} bindable reference to `<img>` element */
export let ref = undefined
/**
* @callback onclick
* @param {MouseEvent & { currentTarget: EventTarget & HTMLImageElement }} event
*/

let sources = []
let img = {}
let background = undefined
/**
* @callback onload
* @param {Event & { currentTarget: EventTarget & Element }} event
*/

$: sources = src.sources || {}
$: img = src.img || {}
$: if (len(img)) {
const { lqip } = img
background = lqip ? lqipToBackground(lqip) : undefined
}
/**
* @typedef SvelteImgProps
* @property {Object} src
* @property {string | undefined} sizes
* @property {number | undefined} width
* @property {number | undefined} height
* @property {'lazy' | 'eager'} loading
* @property {'async' | 'auto'} decoding
* @property {HTMLImageElement | undefined} ref
* @property {onclick} onclick
* @property {onload} onload
*/

/** @type {SvelteImgProps} */
let {
src = {},
sizes,
width,
height,
loading = 'lazy',
decoding = 'async',
ref = $bindable(),
...rest
} = $props()

let sources = $derived(src.sources || {})
let img = $state(src.img || {})
let background = $state(undefined)

$effect(() => {
if (len(img)) {
const { lqip } = img
background = lqip ? lqipToBackground(lqip) : undefined
}
})
</script>

{#if len(img)}
<Picture {sources} {sizes}>
<!-- svelte-ignore a11y-missing-attribute a11y-no-noninteractive-element-interactions -->
<img
{loading}
{decoding}
width={width || img.w || undefined}
height={height || img.h || undefined}
style:background
bind:this={ref}
on:click
on:load
{...$$restProps}
{...rest}
src={img.src}
/>
</Picture>
Expand Down
2 changes: 1 addition & 1 deletion svelte.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vitePreprocess } from '@sveltejs/kit/vite'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import adapter from '@sveltejs/adapter-static'
import { readFileSync } from 'node:fs'

Expand Down