maciekgrzybek / svelte-inview

A Svelte action that monitors an element enters or leaves the viewport.🔥
MIT License
749 stars 23 forks source link

Visiting page directly using Inview with SSR behaves differently #47

Closed leoj3n closed 7 months ago

leoj3n commented 7 months ago

I have a hunch this is a problem with the newer version of svelte...

I haven't figured it out so wanted to document the issue here.

package.json:

    "devDependencies": {
        "@inlang/paraglide-js": "1.2.2",
        "@inlang/paraglide-js-adapter-sveltekit": "^0.2.5",
        "@playwright/test": "^1.41.1",
        "@sveltejs/adapter-vercel": "^5.1.0",
        "@sveltejs/kit": "^2.5.0",
        "@types/cookie": "^0.6.0",
        "eslint": "^8.56.0",
        "eslint-config-prettier": "^9.1.0",
        "eslint-plugin-svelte": "^2.35.1",
        "prettier": "^3.2.4",
        "prettier-plugin-svelte": "^3.1.2",
        "svelte": "^4.2.9",
        "svelte-check": "^3.6.3",
        "svelte-inview": "^4.0.2",
        "svelte-preprocess": "^5.1.3",
        "typescript": "^5.3.3",
        "vite": "^5.0.12",
        "vitest": "^1.2.2",
        "web-vitals": "^3.5.2"
    },

Using latest versions of things...

If you load the page as production directly, this code breaks (the Grid toggle buttons no longer function):

src/lib/components/InviewSrcset.svelte:

<script lang="ts">
    import { inview } from 'svelte-inview';
    import { createEventDispatcher } from 'svelte';
    import Srcset from '$lib/components/Srcset.svelte';

    let ref: HTMLOrSVGElement;
    export let src: string;

    const dispatch = createEventDispatcher();
</script>

<div
    use:inview={{ threshold: 0.5 }}
    on:enter={({ detail }) =>
        dispatch('entry', {
            verticalDirection: detail.scrollDirection.vertical,
            src
        })}
>

    <Srcset
        src={src}
        alt=""
        width="640"
        height="480"
        widths={[640, 960]}
        sizes="(max-width: 640px) 640px, 960px"
        quality={80}
        lazy={true}
        decoding="async"
        bind:this={ref}
    />
</div>

src/routes/photos/+page.svelte:

<script>
    import * as m from '$paraglide/messages';
    import Highlight from '$lib/components/Highlight.svelte';
    import InviewSrcset from '$lib/components/InviewSrcset.svelte';

    let gridenabled = false;

    const yesGridEnabled = () => gridenabled = true;
    const notGridEnabled = () => gridenabled = false;
</script>

<svelte:head>
    <title>{m.pageNamePhotos()}</title>
</svelte:head>

<div>
    <Highlight text={m.pageNamePhotos()} />

    <div class="buttons">
        <button class="button" class:active={!gridenabled} on:click={notGridEnabled}
            >Full View</button
        >
        <button class="button" class:active={gridenabled} on:click={yesGridEnabled}
            >Grid View</button
        >
    </div>

    <div class="grid" class:gridenabled>
        {#each { length: 30 } as _, i}
            <InviewSrcset src="/images/photos/{i + 1}.jpg" />
        {/each}
    </div>
</div>

<style>
    .gridenabled {
        display: grid;
        grid-gap: 0.5rem;
        grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
    }

    @media only screen and (max-width: 974px) {
        .buttons {
            display: none;
        }
    }

    .grid :global(img) {
        width: auto;
        height: auto;
        margin: auto;
        max-width: 100%;
        border-radius: 5px;
        /* border: 4px solid transparent; */
        box-shadow: 0 3px 5px -3px #000;
        background-color: var(--color-bg-3);
    }

    .button:not(.active) {
        cursor: pointer;
        background-color: gray;
    }

    .buttons {
        cursor: default;
        text-align: center;
    }

    .button {
        border: none;
        color: #ffffff;
        position: relative;
        text-align: center;
        padding: 0.7em 1.4em;
        display: inline-block;
        border-radius: 0.15em;
        text-decoration: none;
        box-sizing: border-box;
        margin: 0 0.3em 1em 0;
        text-transform: uppercase;
        background-color: green;
        box-shadow: inset 0 -0.6em 0 -0.35em rgba(0, 0, 0, 0.17);
    }
    .button:active {
        top: 0.1em;
    }
</style>

However, the code is fine using dev.

The code also is fine in prod if you visit the home page first before the page using Inview...

Here is an HTML difference from dev to prod:

Dev:

<div class="buttons s-KG-x8XkR0-JV"><button class="button s-KG-x8XkR0-JV active">Full View</button> <button class="button s-KG-x8XkR0-JV">Grid View</button></div>

Prod direct:

<div class="buttons svelte-h8u9lh"><button class="button svelte-h8u9lh active" data-svelte-h="svelte-1ckqtup">Full View</button> <button class="button svelte-h8u9lh" data-svelte-h="svelte-1om8nfn">Grid View</button></div>

Prod home then photos page:

<div class="buttons svelte-h8u9lh"><button class="button svelte-h8u9lh active">Full View</button> <button class="button svelte-h8u9lh">Grid View</button></div>

It is not clear why visiting the photos page directly causes the data-svelte-h="svelte-1ckqtup" but I assume it must be an SSR issue. If anyone has a had this issue please let me know what I should try.

leoj3n commented 7 months ago

Note that I was experiencing a similar issue when manually rolling following this repo:

https://github.com/donovanh/svelte-image-loading

So probably not specifically an "Inview" issue. Thanks for any insight, and I thought worth documenting for the solution.

Probably a simple fix but not sure...

leoj3n commented 7 months ago

Had to change some svelte-specific settings previously configured to make the page prerendered / no javascript.

src/routes/photos/+page.js:

- import { dev } from '$app/environment';
- export const csr = dev;
- export const prerender = true;
+ export const prerender = false;