Help in Swipe animation

Dear Community,

Is it possible to implement swiperjs swipe animations to collection images?

Here they have details about the code

Here are the demos:

I am looking to try “Effect card” animation.

I saw this video where he implement this animation in webflow.

I have no idea about coding. If anyone who have tried or could tell whether this or similar animation is possible in glideapp, it would be a great help.

Thanks & regards,
Chirag Malik

If the source of an Image component is an array then the Image component turns into a carousel. I’d suggest to try that first because it’s really good.

I think the swipe is impossible for now but you could do something with buttons. Here is my attempt at a carousel with HTML and CSS. Though technically possible it was a lot of effort and the advantage over the native one is minimal…

i.e. in my custom carousel I can change the text with each image vs the native one you can’t change the text with each image.

2 Likes

How dynamic would the list of images need to be? Is it a static number of cards, or could the number of cards vary?

I can reproduce the example in Swiper exactly by copying the code into a Custom Component, but I’m not sure yet how easy it is to make it dynamic.

2 Likes

I’m very close to getting it to work. Only issue is that when I scroll to the end of my array, it isn’t swiping back to the first one.

4 Likes

The way I understand it, I don’t thing it’s supposed to loop around back to the first. I think it’s more of a left to right swipe. That’s how it works on the Swiper site as well.

I’m thinking if this needs to be dynamic, it might be better to build out the html in a javascript column and then feed it into a HTML to URL column.

I guess the other consideration is if the images need to be clickable or call some kind of action. Then the custom component would probably still be needed, but might be a little more difficult to make dynamic.

1 Like

I think this will be difficult since Glide has a swap action to return to another screen, especially the behavior I encountered while on the mobile iPhone. If the swap is too hard it will exit the detail screen or get stuck.
It would be easier to drag to change frames like a slide show than to add animation to the cards.

1 Like

Thanks Eric for the suggestion.
I am using card collection for the images. And the horizontal swipe gives the similar feel as you custom carousel.
Thanks for the efforts :slight_smile:

Hi Jeff,

The numbers are dynamic. If it’s too much of work and have to separately for each card collection, then let it be.
Else, if you can show me it’s possible to achieve this swipe. I’ll try to find a way for other dynamic collection.

This is the app. I have created a new one just to test the swipe. You can see the type of images I have.

Hi @ThinhDinh,

Is it a lot of code? If not, can you share how I can achieve that. If it’s too technical, let it be, I won’t understand much :smiley:

For now, the images don’t need to be clickable!

It uses javascript column and is not yet clickable for advanced actions.

ScreenRecording2024-07-28at11.47.05-ezgif.com-video-to-webp-converter

3 Likes

Looks great!

Is it easy to follow for me as a non-techie guy? :thinking:

Is this what you want and can’t be clicked to go to a details screen, not like the classic Glide feature? I can provide the code for you if you want it.

Yes, they are flashcards to study. No need for it to be clickable. I’ll try and see how it looks.

You only need to combine all your image URLs with the jointlist column. Add the following code to the JavaScript column and set the target configuration of p1 to the jointlist column.
On the frontend, display the result of this JavaScript column using the web embed component. That’s all.

function createSwapCard(p1) {
  let imageUrls = p1.split(',').filter(url => url.trim() !== '');
  if (imageUrls.length === 0) {
    return 'data:text/html;charset=utf-8,' + encodeURIComponent('<html><body><p>No images available.</p></body></html>');
  }

  let html = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Image Swap Card</title>
  <style>
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #0A0A0A;
      margin: 0;
      padding: 0;
      width: 100vw;
      height: 75vh;
      overflow: hidden;
    }
    .card-container {
      position: relative;
      width: 100%;
      height: 100%;
    }
    .card {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #FFF;
      transition: transform 0.3s ease-in-out, z-index 0.3s ease-in-out;
      will-change: transform;
      border-radius: 10px;
      box-shadow: 0 10px 20px rgba(0,0,0,0.2);
    }
    .card img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      pointer-events: none;
      border-radius: 10px;
    }

    @media (max-width: 400px) {
      .card-container {
        width: 100%;
        height: 100%;
      }
    }

    @media (min-width: 601px) {
      .card-container {
        width: 100%;
        max-width: 600px;
        aspect-ratio: 3 / 4;
      }
    }
  </style>
</head>
<body>
  <div class="card-container">
    ${imageUrls.map((url, index) => `<div class="card" data-index="${index}" style="z-index: ${imageUrls.length - index};"><img src="${url}" alt="Image ${index + 1}"></div>`).join('')}
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const cards = document.querySelectorAll('.card');
      let currentIndex = 0;

      function showCard(index) {
        cards.forEach((card, i) => {
          card.style.transform = \`translateY(0) translateX(0) rotate(0deg)\`;
          card.style.opacity = (i >= index) ? 1 : 0;
          card.style.zIndex = (i >= index) ? (cards.length - (i - index)) : 0;
        });
        cards[index].style.zIndex = cards.length;
      }

      function handleSwipe(ev) {
        const threshold = window.innerWidth * 0.25;
        let nextIndex;
        if (Math.abs(ev.deltaX) > threshold) {
          if (ev.deltaX > 0) { 
            nextIndex = (currentIndex - 1 + cards.length) % cards.length;
            cards[nextIndex].style.zIndex = cards.length - 1;
            currentIndex = nextIndex;
          } else { // Swipe ke kiri
            nextIndex = (currentIndex + 1) % cards.length;
            cards[nextIndex].style.zIndex = cards.length - 1;
            currentIndex = nextIndex;
          }
          showCard(currentIndex);
        } else {
          cards[currentIndex].style.transform = \`translateY(0) translateX(0) rotate(0deg)\`;
          cards[currentIndex].style.zIndex = cards.length;
        }
      }

      function handlePan(ev) {
        const deltaX = ev.deltaX;
        cards[currentIndex].style.transform = \`translateX(\${deltaX}px) translateY(0) rotate(\${deltaX / 10}deg)\`;
        
        // Tentukan indeks kartu yang harus terlihat di bawah
        let nextIndex;
        if (deltaX > 0) { 
          nextIndex = (currentIndex - 1 + cards.length) % cards.length;
        } else { // Gerakan ke kiri
          nextIndex = (currentIndex + 1) % cards.length;
        }
        
        cards.forEach((card, i) => {
          if (i === nextIndex) {
            card.style.opacity = 1;
            card.style.zIndex = cards.length - 1;
          } else if (i !== currentIndex) {
            card.style.opacity = 0;
            card.style.zIndex = 0;
          }
        });
      }

      const hammer = new Hammer(document.querySelector('.card-container'));
      hammer.on('panstart', () => {
        cards[currentIndex].style.zIndex = cards.length + 1;
      });
      hammer.on('pan', handlePan);
      hammer.on('panend', handleSwipe);

      showCard(currentIndex);
    });
  </script>
</body>
</html>
`;

  let uri = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
  return uri;
}

return createSwapCard(p1);

2 Likes

Here’s my attempt @Mylengua .

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <title>
      Overlapping Cards with SwiperJS and AlpineJS
    </title>
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/8.4.5/swiper-bundle.min.css"
    />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.10.5/cdn.min.js"
      defer
    ></script>
    <style>
      body {
          display: flex;
          justify-content: center;
          align-items: center;
          height: 100vh;
          margin: 0;
          background-color: #f0f0f0;
      }
      .swiper {
          width: 300px;
          height: 400px;
      }
      .swiper-slide {
          display: flex;
          justify-content: center;
          align-items: center;
          border-radius: 18px;
          font-size: 22px;
          font-weight: bold;
          color: #fff;
      }
      .swiper-slide img {
          width: 100%;
          height: 100%;
          object-fit: cover;
          border-radius: 18px;
      }
    </style>
  </head>
  <body>
    <div x-data="swiperComponent()" x-init="init">
      <div class="swiper" x-ref="swiperContainer">
        <div class="swiper-wrapper" x-ref="swiperWrapper">
          <!-- Slides will be injected here -->
        </div>
      </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/8.4.5/swiper-bundle.min.js"></script>
    <script>
      function swiperComponent() {
        return {
          swiper: null,
          images: [],
          init() {
            this.images = S.images.split(", ");
            this.injectSlides();
            this.$nextTick(() => {
              this.initSwiper();
            });
          },
          injectSlides() {
            const wrapper = this.$refs.swiperWrapper;
            wrapper.innerHTML = ""; // Clear any existing content
            this.images.forEach((image, index) => {
              const slide = document.createElement("div");
              slide.className = "swiper-slide";
              slide.innerHTML = `<img src="${image}" alt="Slide ${
                index + 1
              }">`;
              wrapper.appendChild(slide);
            });
          },
          initSwiper() {
            this.swiper = new Swiper(
              this.$refs.swiperContainer,
              {
                effect: "cards",
                grabCursor: true,
                loop: true,
              }
            );
          },
        };
      }
    </script>
  </body>
</html>
3 Likes

Hi @ThinhDinh

You are amazing. I am sure you already know that :slight_smile: :slight_smile:

1 Like

Thank you so much @Himaladin for the help. It works well. Was finding a way to change according to my images sizes (I have no idea about coding). Was about to disturb you. But now, ThinhDinh helped me with SwipesJs code. Thanks anyway.

1 Like

I was using an old version of Swiper, but I guess you can update it to the latest one to see if there’s any problem.

I tested it on MacOS and iOS, it’s working fine except for some loading time for first card > swiping to last card.

1 Like

can you share tutorial for thisss