Jump to content
Search Community

Recommended Posts

guri1
Posted

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.

Screenshot 2026-05-04 163427.png

Screenshot 2026-05-04 165010.png

FOR MORE CHECK HERE:https://es-d-3609359120260506-019df2b9-53a8-7fc7-8ea0-d22199f1a277.codepen.dev/

Rodrigo
Posted

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
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
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

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...