Skip to content

Optimize Spline rendering using intersection observer #26

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

Open
wants to merge 8 commits into
base: fix/0/optimization-v1
Choose a base branch
from

Conversation

AmirAgassi
Copy link
Member

@AmirAgassi AmirAgassi commented Apr 26, 2025

Description

Lazy load Spline backgrounds in sections only when they're in view.

Linked Issues

maybe

Testing

unlikely

Reviewer Checklist

When reviewing this PR, make sure to keep the following in mind:

  • This PR should not span too many unrelated tickets or changes.
    • If it does, consider breaking it up into smaller PRs.
  • Is the code coverage acceptable?
  • Does the preview deployment work as expected?
  • Does this PR pass all tests?
  • Does this PR have a clear list of issues that it closes?
  • Does the code follow the style guidelines of the project?

Author Checklist

Before opening this PR, make sure the PR:

  • Has an assignee or group of assignees.
  • Has a reviewer or a group of reviewers.
  • Is labelled properly.
  • Has an assigned milestone.

Additionally, make sure that:

  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • Any dependent changes have been merged and published in downstream modules.

@AmirAgassi AmirAgassi added bug Something isn't working enhancement New feature or request frontend Related to the frontend of the project labels Apr 26, 2025
@AmirAgassi AmirAgassi added this to the M0: Anno milestone Apr 26, 2025
@AmirAgassi AmirAgassi self-assigned this Apr 26, 2025
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR conditionally renders the Spline animation using the Intersection Observer API and updates the landing page to include an About section, along with adding new asset imports for Knot animations.

  • Added the About section to Landing.tsx
  • Integrated Intersection Observer-based conditional loading for the Spline animation in UpdatesSection
  • Exported the About component and added Knot assets in the assets index

Reviewed Changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 1 comment.

File Description
src/pages/Landing.tsx Added the About section component to the landing page
src/components/sections/updates.section.tsx Updated Spline loading to use Intersection Observer for conditional rendering
src/components/index.ts Exported the new About component alongside other sections
src/assets/index.ts Imported and exported new Knot animation assets
Files not reviewed (2)
  • package.json: Language not supported
  • pnpm-lock.yaml: Language not supported

@AmirAgassi AmirAgassi changed the base branch from main to fix/0/optimization-v1 April 26, 2025 01:03
@AmirAgassi AmirAgassi force-pushed the fix/-1/spline-conditional-loading branch from 3ecf967 to 34e56bf Compare April 26, 2025 01:05
@AmirAgassi AmirAgassi changed the title Fix/ 1/spline conditional loading Fix/-1/spline conditional loading Apr 26, 2025
@AmirAgassi AmirAgassi force-pushed the fix/-1/spline-conditional-loading branch from 34e56bf to dde28aa Compare April 26, 2025 01:23
@AmirAgassi AmirAgassi changed the title Fix/-1/spline conditional loading Optimize Spline rendering using intersection observer Apr 26, 2025
@juancwu
Copy link

juancwu commented Apr 26, 2025

Try portals:

Courtesy of Claude

// SplineComponent.jsx
import React, { useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import Spline from '@splinetool/react-spline';

const SplinePortal = ({ splineUrl }) => {
  // Reference to the original Spline component
  const splineRef = useRef(null);
  
  // Create a shared Spline instance that will be rendered once
  const SplineInstance = (
    <div className="spline-instance" ref={splineRef}>
      <Spline scene={splineUrl} />
    </div>
  );

  return (
    <>
      {/* Render the original Spline component */}
      {SplineInstance}
    </>
  );
};

// Component that creates portals to display the Spline in multiple locations
const SplinePortals = ({ splineUrl, portalTargetIds }) => {
  const [loaded, setLoaded] = React.useState(false);
  const containerRef = useRef(null);
  
  useEffect(() => {
    // Wait for the original Spline to initialize
    setLoaded(true);
  }, []);

  return (
    <div className="spline-container" ref={containerRef}>
      {/* Render the original Spline (only once) */}
      <div className="original-spline" style={{ position: 'fixed', left: '-9999px' }}>
        <SplinePortal splineUrl={splineUrl} />
      </div>

      {/* Create portals to the target locations */}
      {loaded &&
        portalTargetIds.map((targetId) => {
          const targetElement = document.getElementById(targetId);
          if (!targetElement) return null;

          // Clone the original Spline DOM node and project it to the target location
          return createPortal(
            <div className="spline-portal">
              {/* This will be a reference to the original Spline content */}
              <div 
                className="portal-content"
                ref={(node) => {
                  if (node && containerRef.current) {
                    // Use an intersection observer or another method to only show
                    // the portal content when it's in the viewport
                    const observer = new IntersectionObserver((entries) => {
                      entries.forEach(entry => {
                        if (entry.isIntersecting) {
                          node.innerHTML = '';
                          // Create a reference to the original Spline
                          const splineContainer = containerRef.current.querySelector('.spline-instance');
                          if (splineContainer) {
                            // Use iframe technique to optimize performance
                            const iframe = document.createElement('iframe');
                            iframe.style.border = 'none';
                            iframe.style.width = '100%';
                            iframe.style.height = '100%';
                            node.appendChild(iframe);
                            
                            // Copy the Spline content to iframe
                            iframe.onload = () => {
                              const doc = iframe.contentDocument;
                              doc.body.style.margin = '0';
                              const splineClone = splineContainer.cloneNode(true);
                              doc.body.appendChild(splineClone);
                            };
                            
                            iframe.src = 'about:blank';
                          }
                        }
                      });
                    }, { threshold: 0.1 });
                    
                    observer.observe(node);
                  }
                }}
              />
            </div>,
            targetElement
          );
        })}
    </div>
  );
};

// Usage Example
const App = () => {
  const splineUrl = 'https://prod.spline.design/your-scene-id/scene.splinecode';
  const portalTargets = ['portal-target-1', 'portal-target-2', 'portal-target-3'];
  
  return (
    <div className="app">
      <h1>Spline with React Portals</h1>
      
      {/* Main Spline Portal Manager */}
      <SplinePortals splineUrl={splineUrl} portalTargetIds={portalTargets} />
      
      {/* Portal Target Areas */}
      <div className="content-section">
        <h2>Section 1</h2>
        <div id="portal-target-1" className="portal-target"></div>
      </div>
      
      <div className="content-section">
        <h2>Section 2</h2>
        <div id="portal-target-2" className="portal-target"></div>
      </div>
      
      <div className="content-section">
        <h2>Section 3</h2>
        <div id="portal-target-3" className="portal-target"></div>
      </div>
    </div>
  );
};

export default App;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request frontend Related to the frontend of the project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants