阿里云主机折上折
  • 微信号
Current Site:Index > Resource optimization with lazy load mode

Resource optimization with lazy load mode

Author:Chuan Chen 阅读数:30870人阅读 分类: JavaScript

Lazy Load is a strategy for deferring the loading of resources, with the core idea being to postpone the loading of non-critical resources until they are actually needed. This pattern can significantly improve the initial page load speed, especially for modern web applications that contain a large number of images, videos, or complex components.

Basic Principles of Lazy Loading

Lazy loading replaces static declarative loading with dynamic resource requests. In traditional methods, the browser immediately loads all resources defined by <img> or <script> tags, whereas lazy loading converts these operations into on-demand execution. The implementation mechanisms primarily rely on:

  1. Intersection Observer API: Monitors whether elements enter the viewport.
  2. Scroll Event Listeners: A traditional but effective detection method.
  3. Dynamic Imports: Suitable for on-demand loading of JavaScript modules.
// Basic implementation example
const lazyImages = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imageObserver.unobserve(img);
    }
  });
});

lazyImages.forEach(img => imageObserver.observe(img));

Practical Lazy Loading for Image Resources

Modern browsers natively support lazy loading for images, but a compatibility solution is needed:

<!-- Native implementation -->
<img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="Example">

<!-- Compatibility solution -->
<script>
  if ('loading' in HTMLImageElement.prototype) {
    const images = document.querySelectorAll('img[loading="lazy"]');
    images.forEach(img => {
      img.src = img.dataset.src;
    });
  } else {
    // Use Intersection Observer polyfill
  }
</script>

Advanced optimization techniques include:

  • Using Low-Quality Image Placeholders (LQIP)
  • Resource switching based on network quality
  • Preloading with scroll buffers

Dynamic Imports for JavaScript Modules

Dynamic import syntax supported by bundlers like Webpack:

// Static import
// import module from './module'

// Dynamic import
button.addEventListener('click', async () => {
  const module = await import('./module.js');
  module.doSomething();
});

Combined with React's component-level lazy loading:

const LazyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <LazyComponent />
    </Suspense>
  );
}

Lazy Loading Strategies for Video Resources

Video resources require special handling to avoid automatic preloading:

<video controls preload="none" poster="poster.jpg">
  <source data-src="video.webm" type="video/webm">
  <source data-src="video.mp4" type="video/mp4">
</video>

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const videos = document.querySelectorAll('video[data-src]');
    
    const videoObserver = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const video = entry.target;
          video.querySelectorAll('source').forEach(source => {
            source.src = source.dataset.src;
          });
          video.load();
          videoObserver.unobserve(video);
        }
      });
    }, { threshold: 0.5 });

    videos.forEach(video => videoObserver.observe(video));
  });
</script>

Performance Monitoring and Threshold Adjustment

Effective lazy loading requires continuous monitoring:

// Using the Performance API for measurement
const perfObserver = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log(`Lazy-loaded resource: ${entry.name}`);
    console.log(`Start to render: ${entry.renderTime}`);
  });
});

perfObserver.observe({ type: 'resource', buffered: true });

// Dynamically adjusting Intersection Observer thresholds
function adjustThreshold(connectionSpeed) {
  return connectionSpeed === 'slow' ? 0.1 : 0.5;
}

Framework Integration Solutions

Deep integration methods for mainstream frameworks:

Vue directive implementation:

Vue.directive('lazy', {
  inserted: (el, binding) => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          el.src = binding.value;
          observer.unobserve(el);
        }
      });
    });
    observer.observe(el);
  }
});

Angular-specific module:

@NgModule({
  declarations: [LazyImageDirective]
})
export class LazyModule {}

@Directive({
  selector: '[appLazyImage]'
})
export class LazyImageDirective {
  @Input() appLazyImage: string;

  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    const observer = new IntersectionObserver(/* similar implementation */);
    observer.observe(this.el.nativeElement);
  }
}

Special Considerations for Server-Side Rendering (SSR)

Avoid hydration mismatches in SSR environments:

// Next.js example
import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(
  () => import('../components/HeavyComponent'),
  { 
    loading: () => <Loading />,
    ssr: false // Disable server-side rendering
  }
);

// Or lazy loading with SSR
const DynamicWithSSR = dynamic(
  () => import('../components/ImportantComponent'),
  { loading: () => <Loading /> }
);

Error Handling and Fallback Solutions

Robust implementations require error handling:

const lazyLoad = (element, resourceUrl) => {
  return new Promise((resolve, reject) => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          // Image example
          if (element.tagName === 'IMG') {
            element.onload = () => resolve();
            element.onerror = () => {
              element.src = 'fallback.jpg';
              reject(new Error('Load failed'));
            };
            element.src = resourceUrl;
          }
          observer.unobserve(element);
        }
      });
    }, { threshold: 0.1 });

    observer.observe(element);
    
    // Timeout handling
    setTimeout(() => {
      observer.unobserve(element);
      reject(new Error('Load timeout'));
    }, 5000);
  });
};

Advanced Usage of Modern APIs

Enhancing the experience with new browser APIs:

// Using requestIdleCallback
function lazyLoadIdle() {
  requestIdleCallback(() => {
    const elements = document.querySelectorAll('[data-lazy-idle]');
    elements.forEach(el => {
      if (el.getBoundingClientRect().top < window.innerHeight * 2) {
        loadResource(el);
      }
    });
  });
}

// Using Priority Hints
<link rel="preload" href="critical.jpg" as="image" fetchpriority="high">
<img src="lazy.jpg" loading="lazy" fetchpriority="low">

Optimization Configurations for Build Tools

Example of Webpack's chunking strategy:

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 20000,
      maxSize: 244000,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        }
      }
    }
  }
};

Vite's dynamic import handling:

// Vite-specific syntax
const modules = import.meta.glob('./components/*.vue');

// Converted to
const modules = {
  './components/A.vue': () => import('./components/A.vue'),
  './components/B.vue': () => import('./components/B.vue')
}

Special Optimizations for Mobile Devices

Enhanced strategies for mobile devices:

// Loading strategy based on device memory
if (navigator.deviceMemory < 2) {
  const observer = new IntersectionObserver(/* ... */, {
    threshold: Array(10).fill(0).map((_, i) => i * 0.1)
  });
}

// Connection-aware loading
const connection = navigator.connection;
let lazyThreshold = 0.3;

if (connection) {
  if (connection.saveData) {
    lazyThreshold = 0.8;
  } else if (connection.effectiveType.includes('2g')) {
    lazyThreshold = 0.6;
  }
}

Accessibility Assurance

Ensuring lazy loading does not compromise accessibility:

<img
  src="placeholder.jpg"
  data-src="content.jpg"
  alt="Product display"
  aria-live="polite"
  loading="lazy"
>

<div role="status" aria-busy="true" id="loading-status">
  Loading image...
</div>

<script>
  image.addEventListener('load', () => {
    document.getElementById('loading-status').textContent = 'Image loaded';
    setTimeout(() => {
      document.getElementById('loading-status').textContent = '';
    }, 2000);
  });
</script>

Balancing Performance and User Experience

Experimental methods to find the optimal trigger timing:

// Preloading based on user interaction prediction
let scrollPattern = [];
window.addEventListener('scroll', () => {
  scrollPattern.push(window.scrollY);
  
  if (scrollPattern.length > 5) {
    analyzeScrollPattern();
  }
});

function analyzeScrollPattern() {
  const lastFive = scrollPattern.slice(-5);
  const avgSpeed = lastFive.reduce((a,b,i) => {
    return i > 0 ? a + Math.abs(b - lastFive[i-1]) : a;
  }, 0) / 4;
  
  if (avgSpeed > 50) {
    // Preload when scrolling quickly
    document.querySelectorAll('[data-lazy]').forEach(el => {
      if (el.getBoundingClientRect().top < window.innerHeight * 3) {
        loadResource(el);
      }
    });
  }
  
  if (scrollPattern.length > 10) scrollPattern.shift();
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.