gsap-observer
Installation
SKILL.md
GSAP Observer
Observer provides unified gesture detection across devices - wheel, touch, pointer, and scroll events. Perfect for sliders, swipers, scroll hijacking, and cross-device interactions.
Installation
npm install gsap
import { Observer } from 'gsap/Observer'
gsap.registerPlugin(Observer)
Basic Observer
Create Observer
gsap.registerPlugin(Observer)
Observer.create({
target: window,
type: 'wheel,touch,pointer',
onUp: () => console.log('Up'),
onDown: () => console.log('Down'),
onLeft: () => console.log('Left'),
onRight: () => console.log('Right')
})
Scroll Detection
Observer.create({
target: window,
type: 'scroll',
onUp: () => prevSlide(),
onDown: () => nextSlide()
})
Wheel Detection
Observer.create({
target: window,
type: 'wheel',
onWheel: (self) => {
console.log('Wheel delta:', self.deltaY)
}
})
Event Types
Type Combinations
// Wheel and touch
Observer.create({
type: 'wheel,touch',
onDown: () => nextSlide()
})
// All types (default)
Observer.create({
type: 'wheel,touch,pointer,scroll',
onDown: () => nextSlide()
})
// Only pointer
Observer.create({
type: 'pointer',
onDrag: () => console.log('Dragging')
})
Event Types Explained
- wheel - Mouse wheel, trackpad
- touch - Touch swipes (any device)
- pointer - Pointer/mouse drag when pressed
- scroll - Scrollbar dragging
Callbacks
Direction Callbacks
Observer.create({
target: window,
type: 'wheel,touch',
onUp: (self) => {
console.log('Moving up', self.deltaY, self.velocityY)
},
onDown: (self) => {
console.log('Moving down', self.deltaY, self.velocityY)
},
onLeft: (self) => {
console.log('Moving left', self.deltaX, self.velocityX)
},
onRight: (self) => {
console.log('Moving right', self.deltaX, self.velocityX)
}
})
Drag Callbacks
Observer.create({
target: window,
type: 'pointer',
onPress: (self) => {
console.log('Pressed')
},
onDrag: (self) => {
console.log('Dragging...', self.deltaX, self.deltaY)
},
onDragStart: (self) => {
console.log('Drag started')
},
onDragEnd: (self) => {
console.log('Drag ended')
}
})
Hover Callbacks
Observer.create({
target: '.element',
type: 'pointer',
onHover: (self) => {
console.log('Hovering')
},
onHoverEnd: (self) => {
console.log('Hover ended')
}
})
Change Callback
Observer.create({
target: window,
type: 'wheel,touch',
onChange: (self) => {
console.log('Direction changed')
}
})
Configuration Options
Tolerance
Observer.create({
target: window,
type: 'wheel,touch',
tolerance: 10, // Minimum movement before triggering
onUp: () => nextSlide(),
onDown: () => prevSlide()
})
Drag Minimum
Observer.create({
target: window,
type: 'pointer',
dragMinimum: 10, // Minimum pixels for drag
onDrag: (self) => {
console.log('Dragging')
}
})
Wheel Speed
Observer.create({
target: window,
type: 'wheel',
wheelSpeed: 1.5, // Multiplier for wheel events
onWheel: (self) => {
console.log('Wheel:', self.deltaY)
}
})
Prevent Default
Observer.create({
target: window,
type: 'wheel,touch',
preventDefault: true, // Prevent default browser behavior
onWheel: (self) => {
console.log('Wheel handled')
}
})
Ignore Elements
Observer.create({
target: window,
type: 'wheel,touch',
// Ignore elements with this selector
ignore: '.no-scroll, .modal',
onWheel: (self) => {
console.log('Wheel on scrollable area')
}
})
Debounce
Observer.create({
target: window,
type: 'wheel',
debounce: 100, // Debounce events by 100ms
onWheel: (self) => {
console.log('Debounced wheel')
}
})
Accessing Data
Delta and Velocity
Observer.create({
target: window,
type: 'wheel,touch',
onDown: (self) => {
console.log('Delta Y:', self.deltaY)
console.log('Delta X:', self.deltaX)
console.log('Velocity Y:', self.velocityY)
console.log('Velocity X:', self.velocityX)
console.log('Wheel delta:', self.wheelDelta)
}
})
Event Details
Observer.create({
target: window,
type: 'pointer',
onPress: (self) => {
console.log('Start X:', self.startX)
console.log('Start Y:', self.startY)
},
onDrag: (self) => {
console.log('Current X:', self.x)
console.log('Current Y:', self.y)
console.log('Event:', self.event)
}
})
Check Active State
Observer.create({
target: window,
type: 'pointer',
onDrag: (self) => {
if (self.isDragging) {
console.log('Currently dragging')
}
}
})
Patterns
Simple Slider
let currentSlide = 0
const slides = document.querySelectorAll('.slide')
const totalSlides = slides.length
const observer = Observer.create({
target: window,
type: 'wheel,touch',
tolerance: 50,
onUp: () => {
currentSlide = (currentSlide - 1 + totalSlides) % totalSlides
goToSlide(currentSlide)
},
onDown: () => {
currentSlide = (currentSlide + 1) % totalSlides
goToSlide(currentSlide)
}
})
function goToSlide(index) {
gsap.to('.slides-container', {
x: -index * 100 + '%',
duration: 0.6,
ease: 'power2.out'
})
}
Swiper with Momentum
let currentSlide = 0
const slides = document.querySelectorAll('.slide')
const totalSlides = slides.length
Observer.create({
target: '.swiper',
type: 'wheel,touch,pointer',
dragMinimum: 50,
onDragStart: (self) => {
gsap.killTweensOf('.slides-container')
},
onDrag: (self) => {
gsap.set('.slides-container', {
x: currentSlide * -100 + self.deltaX + '%'
})
},
onDragEnd: (self) => {
if (self.deltaX < -50) {
currentSlide = (currentSlide + 1) % totalSlides
} else if (self.deltaX > 50) {
currentSlide = (currentSlide - 1 + totalSlides) % totalSlides
}
gsap.to('.slides-container', {
x: currentSlide * -100 + '%',
duration: 0.6,
ease: 'power2.out'
})
}
})
Scroll Hijacking
let currentSection = 0
const sections = document.querySelectorAll('.section')
const totalSections = sections.length
let isScrolling = false
Observer.create({
target: window,
type: 'scroll',
tolerance: 50,
onUp: () => {
if (isScrolling) return
isScrolling = true
currentSection = Math.max(0, currentSection - 1)
scrollToSection(currentSection)
setTimeout(() => { isScrolling = false }, 1000)
},
onDown: () => {
if (isScrolling) return
isScrolling = true
currentSection = Math.min(totalSections - 1, currentSection + 1)
scrollToSection(currentSection)
setTimeout(() => { isScrolling = false }, 1000)
}
})
function scrollToSection(index) {
gsap.to(window, {
scrollTo: { y: index * window.innerHeight },
duration: 1,
ease: 'power2.inOut'
})
}
Interactive Map
Observer.create({
target: '.map-container',
type: 'pointer',
onDragStart: (self) => {
gsap.set('.map-content', { clearProps: 'x,y' })
},
onDrag: (self) => {
gsap.set('.map-content', {
x: self.deltaX,
y: self.deltaY
})
},
onDragEnd: (self) => {
// Add momentum
gsap.to('.map-content', {
x: self.x + self.velocityX * 20,
y: self.y + self.velocityY * 20,
duration: 0.5,
ease: 'power2.out'
})
}
})
Gallery Navigation
const gallery = document.querySelector('.gallery')
const observer = Observer.create({
target: gallery,
type: 'wheel,touch',
tolerance: 30,
onUp: () => navigateGallery(-1),
onDown: () => navigateGallery(1)
})
function navigateGallery(direction) {
const current = gallery.querySelector('.active')
const items = gallery.querySelectorAll('.gallery-item')
const currentIndex = Array.from(items).indexOf(current)
const nextIndex = (currentIndex + direction + items.length) % items.length
items.forEach(item => item.classList.remove('active'))
items[nextIndex].classList.add('active')
gsap.to(gallery, {
x: -nextIndex * 100 + '%',
duration: 0.5,
ease: 'power2.out'
})
}
Velocity-Based Actions
Threshold Detection
Observer.create({
target: window,
type: 'wheel,touch',
onDragEnd: (self) => {
if (self.velocityY < -2) {
console.log('Fast swipe up')
} else if (self.velocityY > 2) {
console.log('Fast swipe down')
}
}
})
Momentum Scrolling
let momentum = { x: 0, y: 0 }
Observer.create({
target: '.scroll-container',
type: 'pointer',
onDrag: (self) => {
momentum.x = self.velocityX
momentum.y = self.velocityY
gsap.set('.content', {
x: self.deltaX,
y: self.deltaY
})
},
onDragEnd: (self) => {
// Continue with momentum
gsap.to('.content', {
x: self.x + momentum.x * 50,
y: self.y + momentum.y * 50,
duration: 1,
ease: 'power2.out'
})
}
})
Instance Methods
const observer = Observer.create({
target: window,
type: 'wheel',
onWheel: () => console.log('Wheel')
})
// Enable/disable
observer.enable()
observer.disable()
// Kill observer
observer.kill()
// Update configuration
observer.update({
tolerance: 20
})
Static Methods
// Get all observers
const observers = Observer.getAll()
// Kill all observers
Observer.getAll().forEach(obs => obs.kill())
// Create global config
Observer.defaults = {
type: 'wheel,touch',
tolerance: 50
}
Performance Tips
Debounce Heavy Callbacks
Observer.create({
target: window,
type: 'wheel',
debounce: 100, // Debounce by 100ms
onWheel: gsap.utils.throttle(() => {
// Heavy operation
updateLayout()
}, 100)
})
Use Right Event Type
// ❌ Using all types when only need one
Observer.create({
type: 'wheel,touch,pointer,scroll',
onWheel: () => console.log('Wheel')
})
// ✅ Use only needed type
Observer.create({
type: 'wheel',
onWheel: () => console.log('Wheel')
})
Prevent Default Carefully
// ❌ Always preventing default
Observer.create({
preventDefault: true,
onWheel: () => console.log('Wheel')
})
// ✅ Conditional prevent default
Observer.create({
onWheel: (self) => {
if (shouldPreventDefault(self)) {
self.event.preventDefault()
}
}
})
Common Mistakes
1. Not Ignoring Scrollable Areas
// ❌ Observer blocks scrolling on all elements
Observer.create({
preventDefault: true,
onWheel: () => console.log('Wheel')
})
// ✅ Ignore scrollable elements
Observer.create({
ignore: '.scrollable-content, textarea',
onWheel: (self) => {
self.event.preventDefault()
console.log('Wheel')
}
})
2. Wrong Target
// ❌ Observer on wrong element
Observer.create({
target: '.child',
type: 'wheel',
onWheel: () => console.log('Wheel')
})
// ✅ Observer on container/window
Observer.create({
target: '.container',
type: 'wheel',
onWheel: () => console.log('Wheel')
})
3. Not Debouncing
// ❌ Fires on every scroll tick
Observer.create({
type: 'scroll',
onUp: () => updateLayout() // Expensive!
})
// ✅ Debounce expensive operations
Observer.create({
type: 'scroll',
debounce: 100,
onUp: () => updateLayout()
})
Best Practices
- Choose right event types - Only use what you need
- Set appropriate tolerance - Prevent accidental triggers
- Use ignore for scrollable areas - Don't block necessary scrolling
- Debounce expensive callbacks - Performance critical
- Leverage velocity - Create momentum-based interactions
- Test on multiple devices - Touch vs wheel behavior differs
- Clean up observers - Kill when no longer needed
- Consider accessibility - Provide alternatives for scroll hijacking
Quick Reference
| Feature | Method |
|---|---|
| Create observer | Observer.create({ target, type, onUp }) |
| Event types | 'wheel', 'touch', 'pointer', 'scroll' |
| Direction callbacks | onUp, onDown, onLeft, onRight |
| Drag callbacks | onDrag, onDragStart, onDragEnd |
| Access velocity | self.velocityY, self.velocityX |
| Access delta | self.deltaY, self.deltaX |
| Tolerance | tolerance: 50 |
| Prevent default | preventDefault: true |
| Ignore elements | ignore: '.selector' |
| Enable/disable | observer.enable(), observer.disable() |
Weekly Installs
1
Repository
microck/gsap-skillsFirst Seen
6 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
warp1