---
title: 'Merge'
excerpt: 'Collision merge vs true absorb merge — dissolve into the meeting point while the result emerges.'
icon: 'Sparkles'
---

## Collision merge

Tiles meet at `midX`, fade, then the result pops in **after** they vanish. Clear impact, but it can read as a cut.

<ComponentPreview replay>
  <GsapMergePrimitive />

  <DocsFileTabs title="Collision merge">

```tsx title="merge.tsx"
const meetLeft = midX - tileW / 2;
tl.to([left, right], { y: -8, duration: 0.28 })
  .to(left, { x: meetLeft, scale: 0.35, autoAlpha: 0.35, filter: 'blur(1px)', duration: 0.5 }, 'merge')
  .to(right, { x: meetLeft - rightStart, scale: 0.35, autoAlpha: 0.35, filter: 'blur(1px)', duration: 0.5 }, 'merge')
  .add(() => spawnParticles(midX, midY))
  .to([left, right], { autoAlpha: 0, scale: 0.12, duration: 0.22 })
  .fromTo(result, { scale: 0.25, autoAlpha: 0 }, {
    scale: 1, autoAlpha: 1, duration: 0.55, ease: 'elastic.out(1, 0.55)',
  });
```

```html title="merge-markup.html"
<div class="relative h-24 w-56 overflow-visible">
  <div class="gsap-merge-left absolute size-16 rounded-xl border"></div>
  <span class="gsap-merge-plus absolute">+</span>
  <div class="gsap-merge-right absolute size-16 rounded-xl border"></div>
  <div class="gsap-merge-result absolute size-16 rounded-xl"></div>
</div>
```

  </DocsFileTabs>
</ComponentPreview>

## Absorb merge (true merge)

Not collision-and-swap: the pieces **dissolve into** the meeting point while the result **emerges from the same space**.

1. Lift
2. Slide toward each other with a connecting **beam**
3. Overlap — particle burst + beam flash
4. **Absorb** — shrink + blur + fade into `midX`
5. **Result** starts mid-absorb (`absorb+=0.06`) with elastic overshoot

<ComponentPreview replay>
  <GsapMergeAbsorb />

  <DocsFileTabs title="Absorb merge">

```tsx title="merge-absorb.tsx"
tl.to([left, right], { y: -6, duration: 0.35, ease: 'power2.out' })
  .to(left, { x: leftOverlapX, duration: 0.5, ease: 'power2.inOut' }, 'converge')
  .to(right, { x: rightOverlapX, duration: 0.5, ease: 'power2.inOut' }, 'converge')
  .to(beam, { autoAlpha: 0.75, scaleX: 1, duration: 0.4 }, 'converge')
  .add(() => spawnParticles(midX, midY))
  .to(beam, { autoAlpha: 0, scaleX: 1.55, filter: 'brightness(2.2)', duration: 0.3 })
  // absorb + emerge overlap — this is the merge
  .to(left, {
    x: leftTravelX, scale: 0.55, autoAlpha: 0, filter: 'blur(3px)',
    duration: 0.35, ease: 'power2.in',
  }, 'absorb')
  .to(right, {
    x: rightTravelX, scale: 0.7, autoAlpha: 0, filter: 'blur(2px)',
    duration: 0.35, ease: 'power2.in',
  }, 'absorb')
  .fromTo(result,
    { scale: 0.3, autoAlpha: 0, filter: 'blur(8px) brightness(2)' },
    {
      scale: 1, autoAlpha: 1, filter: 'blur(0px) brightness(1)',
      duration: 0.65, ease: 'elastic.out(1, 0.5)',
    },
    'absorb+=0.06'
  );
```

```html title="merge-absorb-markup.html"
<div class="relative h-24 w-56 overflow-visible">
  <div class="gsap-merge-beam absolute h-0.5 w-20 rounded-full bg-accent"></div>
  <div class="gsap-merge-left absolute size-16 rounded-xl border"></div>
  <span class="gsap-merge-plus absolute">+</span>
  <div class="gsap-merge-right absolute size-16 rounded-xl border"></div>
  <div class="gsap-merge-result absolute size-16 rounded-xl"></div>
</div>
```

  </DocsFileTabs>
</ComponentPreview>

<Callout variant="tip" title="Overlap the labels">
  Start the result tween on `absorb+=0.06`, not after absorb finishes. That handoff is what sells reconstitution instead of a cut.
</Callout>

## Use case — glyph merge

`ে` + `ক` → `কে` using the absorb variation.

<ComponentPreview replay>
  <GsapGlyphMerge />

  <DocsFileTabs title="Glyph merge">

```tsx title="glyph-merge.tsx"
// same absorb timeline; result shows কে
.to(left, { x: leftOverlapX, duration: 0.5 }, 'converge')
.to(right, { x: rightOverlapX, duration: 0.5 }, 'converge')
.to(beam, { autoAlpha: 0.75, scaleX: 1, duration: 0.4 }, 'converge')
.add(() => spawnParticles(midX, midY))
.to(left, { x: leftTravelX, scale: 0.55, autoAlpha: 0, filter: 'blur(3px)', duration: 0.35 }, 'absorb')
.to(right, { x: rightTravelX, scale: 0.7, autoAlpha: 0, filter: 'blur(2px)', duration: 0.35 }, 'absorb')
.fromTo(result,
  { scale: 0.3, autoAlpha: 0, filter: 'blur(8px) brightness(2)' },
  { scale: 1, autoAlpha: 1, duration: 0.65, ease: 'elastic.out(1, 0.5)' },
  'absorb+=0.06'
);
```

```html title="glyph-merge-markup.html"
<div class="relative h-24 w-56 overflow-visible">
  <div class="gsap-merge-beam absolute h-0.5 w-20 bg-accent"></div>
  <div class="gsap-merge-left absolute">ে</div>
  <span class="gsap-merge-plus">+</span>
  <div class="gsap-merge-right absolute">ক</div>
  <div class="gsap-merge-result absolute">কে</div>
</div>
```

  </DocsFileTabs>
</ComponentPreview>
