UNPKG

gsap-dattebayo

Version:

The ultimate GSAP-powered scroll animation library - Simple as AOS, powerful as GSAP. Modern animations for 2025-2026 web trends.

480 lines (473 loc) 15.3 kB
/*! * GSAP Dattebayo v0.1.0-alpha.1 * The ultimate GSAP-powered scroll animation library * https://github.com/serdjan/gsap-dattebayo * * Copyright 2025 GSAP Dattebayo * Released under the MIT License */ 'use strict'; var gsap = require('gsap'); var ScrollTrigger = require('gsap/ScrollTrigger'); require('gsap/SplitText'); /** * Helper utilities for GSAP Dattebayo */ /** * Convert selector or element to array of HTMLElements */ function toArray(target) { if (typeof target === 'string') { return Array.from(document.querySelectorAll(target)); } if (target instanceof HTMLElement) { return [target]; } if (target instanceof NodeList) { return Array.from(target); } if (Array.isArray(target)) { return target; } return []; } /** * Parallax scroll effects * Uses GSAP ScrollTrigger for smooth parallax animations */ /** * Basic parallax - Elements move at different speeds while scrolling */ function parallax(target, options = {}) { const { speed = 0.5, direction = 'vertical', scrub = true, start = 'top bottom', end = 'bottom top', markers = false } = options; const elements = toArray(target); const triggers = []; elements.forEach(element => { const yMove = direction === 'vertical' ? (speed - 1) * 100 : 0; const xMove = direction === 'horizontal' ? (speed - 1) * 100 : 0; const trigger = ScrollTrigger.create({ trigger: element, start, end, scrub, markers, onUpdate: self => { gsap.to(element, { y: yMove * self.progress, x: xMove * self.progress, force3D: true, overwrite: 'auto' }); } }); triggers.push(trigger); }); return triggers; } /** * Parallax speed - Simple speed-based parallax (like Locomotive Scroll) */ function parallaxSpeed(target, options = {}) { const { speed = 1, scrub = 1, markers = false } = options; const elements = toArray(target); const triggers = []; elements.forEach(element => { gsap.to(element, { y: () => -(element.offsetHeight * (1 - speed)), ease: 'none', scrollTrigger: { trigger: element, start: 'top bottom', end: 'bottom top', scrub, markers, invalidateOnRefresh: true } }); const st = ScrollTrigger.getById(element.dataset.scrollTriggerId || ''); if (st) triggers.push(st); }); return triggers; } /** * Parallax layers - Multi-layer parallax effect */ function parallaxLayers(container, options = {}) { const { scrub = 1, markers = false } = options; const containerElement = typeof container === 'string' ? document.querySelector(container) : container; if (!containerElement) return []; const layers = toArray(containerElement.querySelectorAll('[data-speed]')); const triggers = []; layers.forEach(layer => { const speed = parseFloat(layer.dataset.speed || '1'); gsap.to(layer, { y: () => -(layer.clientHeight * (1 - speed)), ease: 'none', scrollTrigger: { trigger: containerElement, start: 'top bottom', end: 'bottom top', scrub, markers, invalidateOnRefresh: true } }); const st = ScrollTrigger.getById(layer.dataset.scrollTriggerId || ''); if (st) triggers.push(st); }); return triggers; } /** * Parallax 3D - Perspective-based parallax */ function parallax3D(target, options = {}) { const { speed = 0.5, scrub = 1, markers = false } = options; const elements = toArray(target); const triggers = []; elements.forEach(element => { gsap.set(element, { transformPerspective: 1000, transformStyle: 'preserve-3d' }); gsap.to(element, { z: () => 200 * (1 - speed), rotationX: () => 10 * (1 - speed), ease: 'none', scrollTrigger: { trigger: element, start: 'top bottom', end: 'bottom top', scrub, markers } }); const st = ScrollTrigger.getById(element.dataset.scrollTriggerId || ''); if (st) triggers.push(st); }); return triggers; } /** * Parallax rotate - Rotation-based parallax */ function parallaxRotate(target, options = {}) { const { speed = 1, scrub = 1, markers = false } = options; const elements = toArray(target); const triggers = []; elements.forEach(element => { gsap.to(element, { rotation: () => 360 * speed, ease: 'none', scrollTrigger: { trigger: element, start: 'top bottom', end: 'bottom top', scrub, markers } }); const st = ScrollTrigger.getById(element.dataset.scrollTriggerId || ''); if (st) triggers.push(st); }); return triggers; } /** * Scroll reveal animations * Elements animate when they enter the viewport */ /** * Scroll reveal - Animate elements as they enter viewport */ function scrollReveal(target, options = {}) { const { animation = 'fadeUp', duration = 1, ease = 'power2.out', start = 'top 80%', end = 'bottom 20%', once = false, markers = false, distance = 50, stagger = 0 } = options; const elements = toArray(target); const triggers = []; // Animation presets const animations = { fade: { opacity: 0 }, fadeUp: { opacity: 0, y: distance }, fadeDown: { opacity: 0, y: -distance }, fadeLeft: { opacity: 0, x: distance }, fadeRight: { opacity: 0, x: -distance }, zoom: { opacity: 0, scale: 0.5 }, slide: { x: -100 } }; const fromVars = animations[animation] || animations.fadeUp; elements.forEach((element, i) => { gsap.from(element, { ...fromVars, duration, ease, delay: i * stagger, scrollTrigger: { trigger: element, start, end, markers, once, toggleActions: once ? 'play none none none' : 'play none none reverse' } }); const st = ScrollTrigger.getById(element.dataset.scrollTriggerId || ''); if (st) triggers.push(st); }); return triggers; } /** * Batch scroll reveal - Optimized for many elements */ function batchScrollReveal(target, options = {}) { const { animation = 'fadeUp', duration = 1, ease = 'power2.out', start = 'top 80%', stagger = 0.1, distance = 50, once = false } = options; const animations = { fade: { opacity: 0 }, fadeUp: { opacity: 0, y: distance }, fadeDown: { opacity: 0, y: -distance }, fadeLeft: { opacity: 0, x: distance }, fadeRight: { opacity: 0, x: -distance }, zoom: { opacity: 0, scale: 0.5 } }; const fromVars = animations[animation] || animations.fadeUp; return ScrollTrigger.batch(target, { onEnter: batch => { gsap.from(batch, { ...fromVars, duration, ease, stagger, overwrite: 'auto' }); }, onLeaveBack: !once ? batch => { gsap.to(batch, { ...fromVars, duration: duration * 0.5, overwrite: 'auto' }); } : undefined, start, once }); } /** * Pin section - Pin element while scrolling */ function pinSection(target, options = {}) { const { start = 'top top', end = '+=100%', pin = true, scrub = false, markers = false } = options; const element = typeof target === 'string' ? document.querySelector(target) : target; if (!element) throw new Error('Pin target not found'); return ScrollTrigger.create({ trigger: element, start, end, pin, scrub, markers }); } /** * Scrub animation - Animation tied to scroll position */ function scrubAnimation(target, animationVars, options = {}) { const { start = 'top bottom', end = 'bottom top', scrub = 1, markers = false } = options; const elements = toArray(target); const tweens = []; elements.forEach(element => { const tween = gsap.to(element, { ...animationVars, ease: 'none', scrollTrigger: { trigger: element, start, end, scrub, markers } }); tweens.push(tween); }); return tweens; } /** * Horizontal scroll - Horizontal scrolling section */ function horizontalScroll(container, options = {}) { const { scrub = 1, pin = true, markers = false } = options; const containerElement = typeof container === 'string' ? document.querySelector(container) : container; if (!containerElement) throw new Error('Horizontal scroll container not found'); const sections = containerElement.querySelectorAll('[data-scroll-section]'); const totalWidth = Array.from(sections).reduce((acc, section) => acc + section.offsetWidth, 0); return gsap.to(sections, { xPercent: -100 * (sections.length - 1), ease: 'none', scrollTrigger: { trigger: containerElement, pin, scrub, markers, end: () => `+=${totalWidth}`, invalidateOnRefresh: true } }).scrollTrigger; } /** * Scroll progress indicators * Visual feedback for scroll position */ /** * Scroll progress bar - Page-level progress indicator */ function scrollProgress(options = {}) { const { direction = 'horizontal', thickness = 4, color = '#3b82f6', position = 'top' } = options; // Create progress bar element const progressBar = document.createElement('div'); progressBar.className = 'gsap-scroll-progress'; // Style the progress bar const isHorizontal = direction === 'horizontal'; Object.assign(progressBar.style, { position: 'fixed', [position]: '0', [isHorizontal ? 'left' : 'top']: '0', [isHorizontal ? 'width' : 'height']: '0%', [isHorizontal ? 'height' : 'width']: `${thickness}px`, backgroundColor: color, zIndex: '9999', transformOrigin: '0 0' }); document.body.appendChild(progressBar); // Animate on scroll gsap.to(progressBar, { [isHorizontal ? 'width' : 'height']: '100%', ease: 'none', scrollTrigger: { trigger: document.body, start: 'top top', end: 'bottom bottom', scrub: true } }); return progressBar; } /** * Section progress - Progress for specific section */ function sectionProgress(target, options = {}) { const { direction = 'vertical', thickness = 4, color = '#3b82f6', position = 'left' } = options; const targetElement = typeof target === 'string' ? document.querySelector(target) : target; if (!targetElement) throw new Error('Section progress target not found'); // Create progress indicator const indicator = document.createElement('div'); indicator.className = 'gsap-section-progress'; const isVertical = direction === 'vertical'; Object.assign(indicator.style, { position: 'absolute', [position]: '0', [isVertical ? 'top' : 'left']: '0', [isVertical ? 'height' : 'width']: '0%', [isVertical ? 'width' : 'height']: `${thickness}px`, backgroundColor: color, transformOrigin: '0 0' }); targetElement.style.position = 'relative'; targetElement.appendChild(indicator); // Animate based on section scroll gsap.to(indicator, { [isVertical ? 'height' : 'width']: '100%', ease: 'none', scrollTrigger: { trigger: targetElement, start: 'top top', end: 'bottom bottom', scrub: true } }); return indicator; } /** * Circular progress - Circular scroll indicator */ function circularProgress(options = {}) { const { size = 60, thickness = 4, color = '#3b82f6', position = 'bottom' } = options; // Create SVG circle const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', String(size)); svg.setAttribute('height', String(size)); svg.style.position = 'fixed'; svg.style[position] = '20px'; svg.style.right = '20px'; svg.style.zIndex = '9999'; const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); const radius = (size - thickness) / 2; const circumference = 2 * Math.PI * radius; circle.setAttribute('cx', String(size / 2)); circle.setAttribute('cy', String(size / 2)); circle.setAttribute('r', String(radius)); circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', color); circle.setAttribute('stroke-width', String(thickness)); circle.setAttribute('stroke-dasharray', String(circumference)); circle.setAttribute('stroke-dashoffset', String(circumference)); circle.setAttribute('transform', `rotate(-90 ${size / 2} ${size / 2})`); svg.appendChild(circle); document.body.appendChild(svg); // Animate circle gsap.to(circle, { strokeDashoffset: 0, ease: 'none', scrollTrigger: { trigger: document.body, start: 'top top', end: 'bottom bottom', scrub: true } }); return svg; } /** * Scroll percentage - Update element with scroll percentage */ function scrollPercentage(target, options = {}) { const targetElement = typeof target === 'string' ? document.querySelector(target) : target; if (!targetElement) throw new Error('Scroll percentage target not found'); return ScrollTrigger.create({ trigger: document.body, start: 'top top', end: 'bottom bottom', onUpdate: self => { const percentage = Math.round(self.progress * 100); targetElement.textContent = `${percentage}%`; } }); } exports.batchScrollReveal = batchScrollReveal; exports.circularProgress = circularProgress; exports.horizontalScroll = horizontalScroll; exports.parallax = parallax; exports.parallax3D = parallax3D; exports.parallaxLayers = parallaxLayers; exports.parallaxRotate = parallaxRotate; exports.parallaxSpeed = parallaxSpeed; exports.pinSection = pinSection; exports.scrollPercentage = scrollPercentage; exports.scrollProgress = scrollProgress; exports.scrollReveal = scrollReveal; exports.scrubAnimation = scrubAnimation; exports.sectionProgress = sectionProgress;