A lightweight JavaScript library for detecting when elements enter or exit the viewport.
npm install @opuu/inviewimportInViewfrom"@opuu/inview";constobserver=newInView(".my-element");observer.on("enter",(event)=>{console.log("Element visible:",event.percentage+"%");});observer.on("exit",(event)=>{console.log("Element hidden");});importInViewfrom"@opuu/inview";constobserver=newInView(".lazy-image");observer.on("enter",(event)=>{constimg=event.target;img.src=img.dataset.src;img.classList.add("loaded");});importInViewfrom"@opuu/inview";constobserver=newInView(".animate-on-scroll");observer.on("enter",(event)=>{event.target.classList.add("animate");});observer.on("exit",(event)=>{event.target.classList.remove("animate");});importInViewfrom"@opuu/inview";constobserver=newInView(".load-more-trigger");observer.on("enter",(event)=>{loadMoreContent();});You can pass options when creating an InView instance:
constobserver=newInView({selector: ".my-element",delay: 100,// Debounce delay in msprecision: "high",// "low", "medium", or "high"single: true,// Only observe first element});| Option | Default | Description |
|---|---|---|
selector | required | CSS selector for elements to observe |
delay | 0 | Debounce delay in milliseconds |
precision | "medium" | Observation precision level |
single | false | Only observe the first matching element |
observer.on("enter",callback);// Element enters viewportobserver.on("exit",callback);// Element exits viewportobserver.pause();// Pause observationobserver.resume();// Resume observationobserver.setDelay(100);// Update debounce delayobserver.destroy();// Clean upCallbacks receive an event object with:
{percentage: 75,// Visibility percentage (0-100)target: Element,// The observed elementtime: 1234567890,// Timestampevent: "enter"|"exit"// Event type// ... other properties}<scripttype="module">importInViewfrom"https://cdn.jsdelivr.net/npm/@opuu/inview/dist/inview.js";</script>MIT
importInViewfrom"@opuu/inview";constimageObserver=newInView(".lazy-image");imageObserver.on("enter",(event)=>{constimg=event.target;if(img.dataset.src){img.src=img.dataset.src;img.removeAttribute("data-src");img.classList.add("loaded");}});importInViewfrom"@opuu/inview";constanimationObserver=newInView({selector: ".animate-on-scroll",precision: "medium",});animationObserver.on("enter",(event)=>{if(event.percentage>50){// Trigger when 50% visibleevent.target.classList.add("animate-in");}});animationObserver.on("exit",(event)=>{event.target.classList.remove("animate-in");});importInViewfrom"@opuu/inview";constinfiniteObserver=newInView({selector: ".load-more-trigger",single: true,delay: 200,// Debounce to prevent multiple rapid loads});infiniteObserver.on("enter",async(event)=>{try{constnewContent=awaitloadMoreContent();document.getElementById("content").appendChild(newContent);}catch(error){console.error("Failed to load content:",error);}});importInViewfrom"@opuu/inview";constperformanceObserver=newInView({selector: ".track-visibility",delay: 300,// Debounce analytics calls});performanceObserver.on("enter",(event)=>{// Track when important content becomes visibleanalytics.track("element_viewed",{element_id: event.target.id,visibility_percentage: event.percentage,timestamp: event.time,});});import{useEffect,useRef}from"react";importInViewfrom"@opuu/inview";functionLazyImage({ src, alt }){constimgRef=useRef(null);useEffect(()=>{constobserver=newInView({selector: imgRef.current,single: true,});observer.on("enter",(event)=>{event.target.src=src;event.target.classList.add("loaded");});return()=>observer.destroy();},[src]);return<imgref={imgRef}alt={alt}className="lazy-image"/>;}For Vue.js applications, use the dedicated @opuu/inview-vue package which provides v-inview and v-outview directives.
npm install @opuu/inview-vueimport{Component,ElementRef,OnInit,OnDestroy}from"@angular/core";importInViewfrom"@opuu/inview";
@Component({selector: "app-lazy-content",template: '<div class="lazy-content">Content</div>',})exportclassLazyContentComponentimplementsOnInit,OnDestroy{privateobserver: InView;constructor(privateelementRef: ElementRef){}ngOnInit(){this.observer=newInView({selector: this.elementRef.nativeElement,single: true,});this.observer.on("enter",(event)=>{event.target.classList.add("visible");});}ngOnDestroy(){this.observer?.destroy();}}- Use appropriate precision levels: Start with "medium" and only use "high" when necessary
- Clean up observers: Always call
destroy()when components unmount - Debounce rapid changes: Use the
delayoption for expensive operations - Single element optimization: Use
single: truewhen observing only one element
// Good: Clean up when doneconstobserver=newInView(".my-element");// ... use observerobserver.destroy();// Important!// Good: Store reference for cleanupclassMyComponent{constructor(){this.observer=newInView(".element");}destroy(){this.observer.destroy();}}InView is built on the Intersection Observer API and supports all modern browsers:
- Chrome 51+
- Firefox 55+
- Safari 12.1+
- Edge 15+
For older browser support, consider using an Intersection Observer polyfill.
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
MIT License - feel free to use this project in your commercial and personal projects.
Obaydur Rahman
- @opuu/inview-vue - Vue.js directives for InView