Search code examples
javascriptsveltesveltekit

Svelte flickering when adding new elements to scroll


Code:

App.svelte

<script>
    import { onMount } from 'svelte';
    import InfiniteScroll from './InfiniteScroll.svelte';
    
    // if the api (like in this example) just have a simple numeric pagination
  let page = 1;
    // but most likely, you'll have to store a token to fetch the next page
    let nextUrl = '';
    // store all the data here.
    let data = [];
    // store the new batch of data here.
    let newBatch = [];
    
    async function fetchData() {
        const response = await fetch(`https://picsum.photos/v2/list?page=${page}&limit=2`);
        newBatch = await response.json();
        console.log(newBatch);
    };
    
    onMount(()=> {
        // load first batch onMount
        fetchData();
    })

  $: data = [
        ...data,
    ...newBatch
  ];
</script>

<div class="parent">
    {#each data as d (d.id)}
        <div class="children">
            <img class="cover" src={d.download_url} alt="download"/>
        </div>
    {/each}
    <InfiniteScroll
      hasMore={newBatch.length}
      threshold={100}
      on:loadMore={() => {page++; fetchData()}}
    />
</div>

<style>
    * {
        box-sizing: border-box;
    margin: 0;
    padding: 0;
    }
    
    .parent {
        max-height: 100vh;
        overflow-y: auto;
        scroll-snap-type: y mandatory;
        margin: 0 auto;
    }
    
    .children {
        position: relative;
        background-color: black;
        min-height: 100vh;
        width: 100%;
        color: white;
        display: flex;
        justify-content: center;
        align-items: center;
        font-weight: 600;
        font-size: 64px;
        scroll-snap-align: center;
        border-bottom: 2px solid #fff;
    }
    
    .cover {
        position: absolute;
        left: 0;
        top: 0;
        background-size: cover;
        max-height: 100vh;
    }
</style>

InfiniteScroll.svelte

<script>
  import { onMount, onDestroy, createEventDispatcher } from "svelte";

  export let threshold = 0;
  export let horizontal = false;
  export let elementScroll;
  export let hasMore = true;

  const dispatch = createEventDispatcher();
  let isLoadMore = false;
  let component;

  $: {
    if (component || elementScroll) {
      const element = elementScroll ? elementScroll : component.parentNode;

      element.addEventListener("scroll", onScroll);
      element.addEventListener("resize", onScroll);
    }
  }

  const onScroll = e => {
    const element = e.target;

    const offset = horizontal
      ? e.target.scrollWidth - e.target.clientWidth - e.target.scrollLeft
      : e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop;

    if (offset <= threshold) {
      if (!isLoadMore && hasMore) {
        dispatch("loadMore");
      }
      isLoadMore = true;
    } else {
      isLoadMore = false;
    }
  };

  onDestroy(() => {
    if (component || elementScroll) {
      const element = elementScroll ? elementScroll : component.parentNode;

      element.removeEventListener("scroll", null);
      element.removeEventListener("resize", null);
    }
  });
</script>

<div bind:this={component} style="width:0px" />

When we scroll to the bottom of the list, the screen flickers and displays the previous element for a moment of time.

Encountering this trouble when trying to implement infinite scrolling, please check the repl. Thanks!


Solution

  • From @H.B.

    InfiniteScroll should not be a component by the way. This is something that actions are made for.

    Answer REPL: https://svelte.dev/repl/cb9b9e77febb496c9f9af118dd077cb6?version=3.55.0

    Used svelte-inview library and inview action.

    Code

    App.svelte

    <script>
        import { onMount } from 'svelte';
        import { inview } from 'svelte-inview';
        
        // if the api (like in this example) just have a simple numeric pagination
      let page = 0;
        // but most likely, you'll have to store a token to fetch the next page
        let nextUrl = '';
        // store all the data here.
        let data = [];
        // store the new batch of data here.
        let newBatch = [];
        // no more data
        let noMoreData = false;
        
        async function fetchData() {
            const response = await fetch(`https://picsum.photos/v2/list?page=${page}&limit=2`);
            newBatch = await response.json();
            console.log(newBatch);
        };
        
        onMount(()=> {
            // load first batch onMount
            // fetchData();
        })
        
        let visible = true;
    
      $: data = [
            ...data,
        ...newBatch
      ];
    </script>
    
    <div class="parent">
        {#each data as d (d.id)}
            <div class="children">
                    <span class="center">{d.id}</span>
            </div>
        {/each}
        <div use:inview={{}} style="height: 10px;" on:change={(e) => {
            if (e.detail.inView && !noMoreData) {
                page++;
                fetchData();
            }
        }}></div>
    </div>
    
    <style>
        * {
            box-sizing: border-box;
        margin: 0;
        padding: 0;
        }
        
        .parent {
            max-height: 100vh;
            overflow-y: scroll;
            scroll-snap-type: y mandatory;
            margin: 0 auto;
        }
        
        .children {
            position: relative;
            background-color: black;
            min-height: 100vh;
            width: 100%;
            color: white;
            display: flex;
            justify-content: center;
            align-items: center;
            font-weight: 600;
            font-size: 64px;
            scroll-snap-align: center;
            border-bottom: 2px solid #fff;
        }
        
        .cover {
            position: absolute;
            left: 0;
            top: 0;
            background-size: cover;
            max-height: 100vh;
        }
    </style>