GeorgeHastings / embed-unicornstudio

https://embed-unicorn-studio.vercel.app
6 stars 0 forks source link

Feature Request: Add Specific Scene Destruction Method #3

Open elliottmangham opened 2 weeks ago

elliottmangham commented 2 weeks ago

Hey 👋

First of all, US is amazing, thank you for such a powerful tool! It's impressive and puts complex effects into an easy-to-use UX.

One issue we've come across in our first test run on an SPA using the code implementation is the lack of specificity in the destroy() method. The code example below is a component, referred to as comp, and we are passing comp.querySelector('#webgl') as the node element. This works a charm. But we have limited options in how we can then destroy the scene without destroying every scene on the page; rather, we want to isolate what we destroy to the component.

Current Workaround:

import * as UnicornStudio from '../vendors/unicornStudio.umd.js';

export default {
    mount(comp) {
        const element = comp.querySelector('#webgl');

        if (element) {
            UnicornStudio.addScene({
                element,
                scale: 0.75,
                lazyLoad: true,
                filePath: '/wp-content/themes/cdrs/assets/static/v05ec6QWcRv6bfjYiI9Z.json',
                altText: 'This is alternate text for SEO',
                ariaLabel: 'This is a description for accessibility',
                production: false, // when true, will hit the global edge CDN
                interactivity: {
                    mouse: {
                        disableMobile: true,
                    },
                },
            })
                .then((scene) => {
                    // Store the scene in the component instance for later use
                    comp.scene = scene;
                    console.log('Scene is ready:', scene);
                })
                .catch((err) => {
                    console.error(err);
                });
        } else {
            console.error('Element not found');
        }
    },

    unmount(comp) {
        console.log('Before destroy:', comp.scene);
        if (comp.scene) {
            try {
                // Check if the scene object has a specific destroy method
                if (typeof comp.scene.destroy === 'function') {
                    comp.scene.destroy();
                    console.log('Scene.destroy() called');
                } else {
                    // Fallback: Manually remove the scene element and clean up references
                    const element = comp.querySelector('#webgl');
                    if (element) {
                        element.remove();
                        console.log('Scene element removed');
                    }
                }

                // Additional cleanup if needed
                comp.scene = null;
                console.log('Scene reference set to null');
            } catch (error) {
                console.error('Error during scene destruction:', error);
            }
        } else {
            console.error('No scene found to destroy');
        }
        console.log('After destroy:', comp.scene);
    },
};

Ideal Solution: To simplify it, something like this would be ideal:

import * as UnicornStudio from '../vendors/unicornStudio.umd.js';

export default {
    mount(comp) {
        const element = comp.querySelector('#webgl');

        if (element) {
            comp.scene = UnicornStudio.addScene({
                element,
                scale: 0.75,
                lazyLoad: true,
                filePath: '/wp-content/themes/cdrs/assets/static/v05ec6QWcRv6bfjYiI9Z.json',
                altText: 'This is alternate text for SEO',
                ariaLabel: 'This is a description for accessibility',
                production: false, // when true, will hit the global edge CDN
                interactivity: {
                    mouse: {
                        disableMobile: true,
                    },
                },
            });
            console.log('Scene is ready:', comp.scene);
        } else {
            console.error('Element not found');
        }
    },

    unmount(comp) {
        console.log('Before destroy:', comp.scene);
        if (comp.scene) {
            try {
                comp.scene.destroy();  // Ideally, this should only destroy the specific scene
                console.log('Scene.destroy() called');
            } catch (error) {
                console.error('Error during scene destruction:', error);
            } finally {
                comp.scene = null;
                console.log('Scene reference set to null');
            }
        } else {
            console.error('No scene found to destroy');
        }
        console.log('After destroy:', comp.scene);
    },
};

This would allow us to bind UnicornStudio.addScene or UnicornStudio.init directly to the component and have a specific destroy method on the scene object, making the cleanup process much more straightforward and less prone to errors. Thank you for considering this feature request!

Feel free to adjust the wording or details according to your specific needs or preferences. This should provide a clear and concise explanation of the issue and a potential solution.

GeorgeHastings commented 2 weeks ago

Thanks for this — to be clear, does the above work? It sounds like from your description that calling this below destroys every scene on the page:

comp.scene.destroy(); // Ideally, this should only destroy the specific scene

Seems like it should. Do you have a before unmount hook? There is a global destroy method but the scene destroy method will only destroy the webgl resources for that scene.

It looks right to me but I'd also inspect comp.scene and make sure that it is in fact a scene and not the global module.

elliottmangham commented 2 weeks ago

Hey @GeorgeHastings,

Thanks for your response!

I'm afraid not, no. The "Current Workaround" does work, but not the "Ideal Solution". Calling comp.scene.destroy, in my case, throws the following error: comp.scene.destroy is not a function

This is the code:

import * as UnicornStudio from '../vendors/unicornStudio.umd.js';

export default {
    mount(comp) {
        const element = comp.querySelector('#webgl');

        if (element) {
            comp.scene = UnicornStudio.addScene({
                element,
                scale: 0.75,
                lazyLoad: true,
                filePath: '/wp-content/themes/cdrs/assets/static/v05ec6QWcRv6bfjYiI9Z.json',
                altText: 'This is alternate text for SEO',
                ariaLabel: 'This is a description for accessibility',
                production: false, // when true, will hit the global edge CDN
                interactivity: {
                    mouse: {
                        disableMobile: true,
                    },
                },
            });
            console.log('Scene is ready:', comp.scene);
        } else {
            console.error('Element not found');
        }
    },

    unmount(comp) {
        console.log('Before destroy:', comp.scene);
        if (comp.scene) {
            try {
                comp.scene.destroy(); // Ideally, this should only destroy the specific scene
                console.log('Scene.destroy() called');
            } catch (error) {
                console.error('Error during scene destruction:', error);
            } finally {
                comp.scene = null;
                console.log('Scene reference set to null');
            }
        } else {
            console.error('No scene found to destroy');
        }
        console.log('After destroy:', comp.scene);
    },
};

And here is a screenshot of unmount executing: Screen - 2024-09-19 at 10  32 32@2x

As you can the nullify works, but the destroy method calls an error.

The comp.scene is a Promise, so I am not sure if that means it is a scene or not.

Thanks!

GeorgeHastings commented 2 weeks ago

Well, addScene is async which is why it returns a promise (and destroy is undefined). So this is the correct way to set it in your component:

.then((scene) => {
    // Store the scene in the component instance for later use
    comp.scene = scene;
    console.log('Scene is ready:', scene);
})

Is that the key difference between your current and ideal solution? Sorry if I'm missing something here. If helpful I have also added scenes to the module so you can access the scenes array with UnicornStudio.scenes if that helps too.