guri1 Posted May 4 Posted May 4 Hey I have create a testimonials with gsap and draggable , I do setup but issues "Draggable create faield" HTML: <div class="testimonials"> <div class="js-testimonials-proxy"></div> <testimonials-slider class="testimonials-wrapper"> <!-- Proxy --> <!-- Images --> <div class="testimonials-images"> <div class="testimonials-images-item">1</div> <div class="testimonials-images-item">2</div> <div class="testimonials-images-item">3</div> <div class="testimonials-images-item">4</div> </div> <!-- Content --> <div class="testimonials-content-slider"> <div class="testimonials-content-wrapper"> <div class="testimonials-content">Content 1</div> <div class="testimonials-content">Content 2</div> <div class="testimonials-content">Content 3</div> <div class="testimonials-content">Content 4</div> </div> <button class="prev">Prev</button> <button class="next">Next</button> </div> </testimonials-slider> </div> import { gsap, Draggable } from '@lib/gsap'; class TestimonialsSlider extends HTMLElement { constructor() { super(); this.onDrag = this.onDrag.bind(this); this.onDragStart = this.onDragStart.bind(this); this.onDragEnd = this.onDragEnd.bind(this); this.onResize = this.onResize.bind(this); this.next = this.next.bind(this); this.prev = this.prev.bind(this); this.show = this.show.bind(this); this.activeElement = 0; this.isManuallyClicked = false; } connectedCallback() { if (this._initialized) return; this._initialized = true; this.section = this.closest('.section'); this.dom = { imagesArray: [...this.querySelectorAll('.testimonials-images-item')], contentsArray: [...this.querySelectorAll('.testimonials-content')], proxy: this.section.querySelector(.js-testimonials-proxy'), slider: this.querySelector('.testimonials-wrapper'), prev: this.querySelector('.prev'), next: this.querySelector('.next'), contentContainer: this.querySelector('.testimonials-content-wrapper') }; console.log('dom', this.dom.proxy); this.initEvents(); this.show(); const init = () => { this.getOffset(); requestAnimationFrame(() => { requestAnimationFrame(() => { this.setGsap(); }); }); }; if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init, { once: true }); } // lazy images ke baad bhi refresh this.dom.imagesArray.forEach(item => { const img = item.querySelector('img'); if (img && !img.complete) { img.addEventListener('load', () => { this.getOffset(); this.draggable?.[0]?.update(); this.onDrag(); }, { once: true }); } }); } /* ===== EVENTS ===== */ initEvents() { this.dom.prev?.addEventListener('click', this.prev); this.dom.next?.addEventListener('click', this.next); window.addEventListener('resize', this.onResize); } /* ===== DRAG ===== */ onDrag() { if (!this.draggable || !this.draggable[0]) return; const x = this.draggable[0].x; this.dom.imagesArray.forEach((el) => { el.actuallPos = x + el.offset; el.distance = Math.abs((x + el.offset) / this.containerSize); const d = Math.min(Math.max(el.distance, 0), 1); el.style.setProperty('--distance', 1 - d); if (x + el.offset <= -this.containerSize) { el.offset += this.fullSize; } else if (x + el.offset >= this.fullSize - this.containerSize) { el.offset -= this.fullSize; } el.style.transform = `translateX(${ el.actuallPos * (1 - d * 0.5) - el.loopOffset }px)`; }); } onDragStart() { this.dom.contentsArray.forEach(t => t.classList.remove('show')); } onDragEnd() { this.isManuallyClicked = false; this.dom.imagesArray.forEach((t, n) => { if (Math.abs(t.distance) < 0.2) { this.activeElement = n; gsap.to(this.dom.contentContainer, { height: this.dom.contentsArray[n].getBoundingClientRect().height }); this.show(); } }); } /* ===== CONTENT ===== */ show() { this.dom.contentsArray.forEach(t => { t.classList.remove('show'); t.classList.add('hidden'); }); const active = this.dom.contentsArray[this.activeElement]; if (active) { active.classList.remove('hidden'); active.classList.add('show'); } } /* ===== NAV ===== */ next() { if (this.isManuallyClicked || !this.draggable?.[0]) return; this.isManuallyClicked = true; this.onDragStart(); gsap.to(this.dom.proxy, { x: Math.round((this.draggable[0].x - this.snap) / this.snap) * this.snap, duration: 0.5, ease: 'power2.out', onUpdate: () => { this.draggable[0]?.update(); this.onDrag(); }, onComplete: this.onDragEnd }); } prev() { if (this.isManuallyClicked || !this.draggable?.[0]) return; this.isManuallyClicked = true; this.onDragStart(); gsap.to(this.dom.proxy, { x: Math.round((this.draggable[0].x + this.snap) / this.snap) * this.snap, duration: 0.5, ease: 'power2.out', onUpdate: () => { this.draggable[0]?.update(); this.onDrag(); }, onComplete: this.onDragEnd }); } /* ===== RESIZE ===== */ onResize() { if (!this.draggable?.[0]) return; this.onDragStart(); this.dom.imagesArray.forEach(t => { t.style.setProperty('--distance', 1); t.style.transform = 'translate3D(0,0,0)'; }); this.getOffset(); this.draggable[0].update(); this.activeElement = 0; gsap.to(this.dom.proxy, { x: 0, duration: 0.5, ease: 'power2.out', onUpdate: () => { this.draggable[0]?.update(); this.onDrag(); }, onComplete: this.onDragEnd }); } /* ===== OFFSET ===== */ getOffset() { this.dom.imagesArray.forEach(t => { t.size = t.getBoundingClientRect(); t.offset = t.size.left - window.innerWidth * 0.5; t.loopOffset = t.offset; }); this.containerSize = Math.min( Math.max(this.getBoundingClientRect().width * 0.7 || 0, 400), 800 ); this.snap = this.dom.imagesArray[1]?.size.width || 300; const last = this.dom.imagesArray[this.dom.imagesArray.length - 1]; this.fullSize = last.offset + last.size.width; const heights = this.dom.contentsArray.map(el => { const prev = el.style.display; el.style.display = 'block'; const h = el.getBoundingClientRect().height; el.style.display = prev; return h; }); this.dom.contentContainer.style.height = Math.max(...heights) + 'px'; } /* ===== GSAP ===== */ setGsap() { if (!this.dom.proxy) { console.warn('[testimonials-slider] proxy element nahi mila'); return; } this.draggable = Draggable.create(this.dom.proxy, { type: 'x', trigger: this, inertia: false, snap: t => Math.round(t / this.snap) * this.snap, overshootTolerance: 0.1, onDrag: this.onDrag, onThrowUpdate: this.onDrag, onDragStart: this.onDragStart, onThrowComplete: this.onDragEnd, onDragEnd: this.onDragEnd }); if (!this.draggable?.[0]) { console.warn('[testimonials-slider] Draggable create failed'); return; } // pehli baar position set karo gsap.set(this.dom.proxy, { x: 0 }); requestAnimationFrame(() => { this.draggable[0].update(); this.onDrag(); }); } } if (!customElements.get('testimonials-slider')) { customElements.define('testimonials-slider', TestimonialsSlider); } NOTE: I want to create a look like this. FOR MORE CHECK HERE:https://es-d-3609359120260506-019df2b9-53a8-7fc7-8ea0-d22199f1a277.codepen.dev/
Rodrigo Posted May 4 Posted May 4 Hi @guri1 and welcome to the GSAP Forums! Can you provide a link to the editable codepen and not the preview? There is not much we can do with a live preview where we can't tweak the code in order to test what could be the problem. Also try to keep the code to it's minimal expression, less than 100 lines would be ideal.
guri1 Posted May 4 Author Posted May 4 2 hours ago, Rodrigo said: Hi @guri1 and welcome to the GSAP Forums! Can you provide a link to the editable codepen and not the preview? There is not much we can do with a live preview where we can't tweak the code in order to test what could be the problem. Also try to keep the code to it's minimal expression, less than 100 lines would be ideal. See the Pen GgNJBey by syscftnk-the-encoder (@syscftnk-the-encoder) on CodePen. Here the link . I’d like to implement the testimonials section using a GSAP Draggable-based interaction for a more smooth and premium experience. The idea is: • The active thumbnail/image will stay centered and scale/emphasize based on position • Users can drag left/right to navigate through testimonials (infinite loop feel) • Navigation buttons (Next/Prev) will also move the thumbnails accordingly • When the active slide changes, the corresponding content will animate vertically (top-to-bottom transition) • Everything will stay in sync — image position, active state, and content animation
guri1 Posted May 4 Author Posted May 4 31 minutes ago, guri1 said: I’d like to implement the testimonials section using a GSAP Draggable-based interaction for a more smooth and premium experience. The idea is: • The active thumbnail/image will stay centered and scale/emphasize based on position • Users can drag left/right to navigate through testimonials (infinite loop feel) • Navigation buttons (Next/Prev) will also move the thumbnails accordingly • When the active slide changes, the corresponding content will animate vertically (top-to-bottom transition) • Everything will stay in sync — image position, active state, and content animation
Rodrigo Posted May 4 Posted May 4 Hi, This definitely has to do with something else in your setup. If I remove the define method from the custom elements registry and run this: Draggable.create(".js-testimonials-proxy", { type: "x", }); Everything works as expected, so this is related to something else in your setup. Unfortunately there is far too much code for us to go through it and see what could be the issue, is beyond the scope of what we do in these free forums since we don't have the time resources to do that four our users. Finally in order to have snap in you Draggable instances you need to include and register the plugin as well as enable it in your Draggable config: gsap.registerPlugin(Draggable, InertiaPlugin); Draggable.create(target, { type: "x", inertia: true, snap: // add your snap code here }); Click the View more link 👇 https://gsap.com/docs/v3/Plugins/Draggable/#inertia Other alternative is live snapping: https://gsap.com/docs/v3/Plugins/Draggable/#snapping Hopefully this helps
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now