Smooth Loading Bar Update Before Scene Progress Callback Fires

I’m setting the loading bar width based on the data from the scene progress callback, but the issue is that the callback only starts firing after a few seconds. Until then, the progress bar just sits at 0, and once the callback starts, it fills up really fast.

Is there a better way to handle this or make the loading bar feel smoother from the beginning?

 switcher.removeEventListener("progress", handleSceneProgress);
const handleSceneProgress = (e) => {
    const loadingPercent: number = e.loaded / e.total;
    if (isInitialLoad.current === 0) {
      setLoadingProgress(loadingPercent);
    }
    setSceneLoadingProgress(loadingPercent);
    if (loadingPercent === 1) {
    }
  };

Hi Ashok!

To create a smoother loading bar experience that provides feedback before the scene progress callback starts firing, you can implement a method to give a preliminary progress update. This could be based on the estimated time it takes to load resources or a random slow increase until the actual loading data starts coming in.

Here’s a refined plan and the corresponding code:

Plan

  1. Create a state variable, estimatedProgress, which starts at 0.
  2. Increase the estimatedProgress at intervals (e.g., every 100 ms) until the actual scene progress callback provides real data.
  3. Within the handleSceneProgress, set the loadingPercent from the callback but keep using estimatedProgress until the first update from the callback.
  4. Transition smoothly between estimatedProgress and the actual loading percentage.

Implementation

let estimatedProgress = 0;
const estimatedProgressInterval = setInterval(() => {
    if (estimatedProgress < 0.9) {
        estimatedProgress += 0.05; // Incremental change
        setSceneLoadingProgress(estimatedProgress); // Update UI
    }
}, 100);

const handleSceneProgress = (e) => {
    const loadingPercent = e.loaded / e.total;
    if (loadingPercent > estimatedProgress) {
        estimatedProgress = loadingPercent; // Better estimated progress
    }

    setLoadingProgress(loadingPercent);
    setSceneLoadingProgress(loadingPercent);

    if (loadingPercent === 1) {
        clearInterval(estimatedProgressInterval); // Clear once loading completes
    }
};

Explanation

  • The setInterval function increases estimatedProgress slowly until actual progress is received.
  • Once the callback starts providing loading updates, you ensure that the actual loading percentage takes precedence.
  • The interval is cleared when the loading is complete to prevent unnecessary updates.

This approach will help make the loading experience feel smoother right from the start while still reflecting real progress as soon as it becomes available.

Feel free to ask if you need more assistance or improvements to this solution!

1 replies left - Get a PRO license for more AI replies.
thread_xoAnNokKqYoZV8iqRLNO5Prb
Reply to this message to continue the conversation

I do thought about what the bot suggested but want to know if there is any better way of handling this?

Hi, do you have a reprodution? The progress event is passed along directly from the three.js GLTFLoader which in turn is passed to the FileLoader.

Is it possible that you have Preload Next/Previous values set in the SceneSwitcher which starts download of subsequent scenes in the background already?

I’ll check if enabling this option doesnt invoke the event as expected since it’s not using GLTFLoader

Update: I don’t seem to be able to reproduce it and I get the progress event as expected.

In this video, I tried to show the issue more clearly. As you can see, the loading bar stays at 0 for a few seconds and only starts updating after that. For reference, I added a console log saying “from progress listener,” and you can see in the console that it only starts appearing after a short delay.
Link : Game creator Ks's Video - Apr 17, 2025
Relevant Code:

ContextRegistry.addContextCreatedCallback(async (_context) => {
    await onNeedleContextCreated();
  });

  const onNeedleContextCreated = async () => {
    if (hasRegisteredSceneListeners.current) return;

    componentDataManager.current = GameObject.findObjectOfType(ComponentDataContainer);
    sceneSwitcher.current = componentDataManager.current.sceneSwitcher;
    const switcher: SceneSwitcher = sceneSwitcher.current;
    if (!switcher) return;
    switcher.removeEventListener("loadscene-start", handleSceneStart);
    switcher.removeEventListener("progress", handleSceneProgress);
    switcher.removeEventListener("loadscene-finished", handleSceneLoadingFinished);
    switcher.removeEventListener("scene-opened", handleSceneOpened);
    switcher.addEventListener("loadscene-start", handleSceneStart);
    switcher.addEventListener("progress", handleSceneProgress);
    switcher.addEventListener("loadscene-finished", handleSceneLoadingFinished);
    switcher.addEventListener("scene-opened", handleSceneOpened);

    hasRegisteredSceneListeners.current = true;
  };

  const handleSceneStart = (e) => {
    console.log(e);
    if (isInitialLoad.current != 0) {
      if (isLoading) setIsLoading(false);
    }
    setIsSceneLoading(true);
    setSceneLoadingProgress(0);
  };

  const handleSceneProgress = (e) => {
    console.log("from progress listener");
    const loadingPercent: number = e.loaded / e.total;
    if (isInitialLoad.current === 0) {
      setLoadingProgress(loadingPercent);
    }
    setSceneLoadingProgress(loadingPercent);
    if (loadingPercent === 1) {
    }
  };

  const handleSceneLoadingFinished = () => {
    isInitialLoad.current++;
  };

Can you share a link to the website?

Hello @Ashokkumar_KR i’ve just tried this in a new stackblitz but can’t reproduce it. The progress callback is passed through to the GLTFLoader and fires immediately

See here the console logs (and logs on screen). If it takes a few seconds to start loading it might be related to your server where the asset is hosted?

Yep. The callback fired immediately once all GLBs were downloaded from the server. Do you have any recommendations for showing a progress bar or some updates during the downloads?

You can just use a regular HTML input slider for example

I mean, is there any callback or something similar to show how many files need to be downloaded and how many have been downloaded so far?

You’re asking about the SceneSwitcher? The SceneSwitcher can only load and display one file at a time. If you have multiple SceneSwitcher components in your scene you can combine their progress.

If you refer to the progress attribute on the <needle-engine> web component it’s event contains a totalProgress01 property as well as index and count properties.

Is there any example to it?. The progress event takes a string i suppose its a function name in string?

Yes it’s a function name. It works the same as the loadfinished example <needle-engine> Configuration | Needle Engine Documentation

Thanks. but im getting undefinded for the property u mentioned.

function onProgress(e) {
        console.log(e.totalProgress01);
      }

It’s inside the detail object. See the attributes table <needle-engine> Configuration | Needle Engine Documentation . Note also that it’s the second argument (onProgress = (ctx, evt) => console.log(evt))

Hey, thanks again, but this isn’t what I meant. Is there any callback fired during the fetch requests so that, before the SceneSwitcher progress callback, users can see that the GLBs are downloading—how much has been downloaded and how many are left?

Before the SceneSwitcher starts downloading the loadscene-start is raised, see the api here:

And as said before: the progress event callback is also straight passed into GLTFLoader and the SceneSwitcher can only load and display one file at a time so there’s always just one download process running per SceneSwitcher component

That being said: Your file is currently much larger than it should be and load much faster once we figure out what’s going on with compression in your project :slight_smile:

Well, the SceneSwitcher is attached to mySceneClone.glb; until it’s fetched, I don’t get any callbacks from it?

I understand know where the confusion stems from:

So in your project mySceneClone is the root file (you added a GLTFObject in Unity to that object) and loaded by Needle Engine. Before that is done the SceneSwitcher is not available / not loaded yet.

I’ll reply via DM since i just opened your project

1 Like

This topic was automatically closed 13 days after the last reply. New replies are no longer allowed.