Skip to example

AnimateView: Clip path

Reveal an image with an animated clip-path circle using AnimateView enter and exit animations.

Vue

Source code

<script setup lang="ts">
import { AnimateView, startTransition } from 'motion-v'
import { ref } from 'vue'

const isOpen = ref(false)

const PHOTO = '/photos/amsterdam-25/image-11.jpg'

const COLLAPSED = 'circle(0% at 50% 50%)'
const REVEALED = 'circle(75% at 50% 50%)'

const enter = {
  clipPath: [COLLAPSED, REVEALED],
  transition: { duration: 0.6, ease: 'easeOut' as const },
}

const exit = {
  clipPath: [REVEALED, COLLAPSED],
  transition: { duration: 0.4, ease: 'easeIn' as const },
}

function toggle() {
  startTransition(() => {
    isOpen.value = !isOpen.value
  })
}
</script>

<template>
  <div class="clip-example">
    <button
      class="clip-button"
      @click="toggle"
    >
      {{ isOpen ? "Close" : "Open" }}
    </button>
    <div class="clip-slot">
      <AnimateView
        v-if="isOpen"
        :enter="enter"
        :exit="exit"
      >
        <img
          class="clip-image"
          :src="PHOTO"
          alt="Amsterdam"
        >
      </AnimateView>
    </div>
  </div>
</template>

<style>
#sandbox {
  justify-content: center;
}
</style>

<style scoped>
.clip-example {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.clip-button {
  padding: 10px 32px;
  background-color: var(--accent);
  color: var(--black);
  border: none;
  border-radius: 10px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

.clip-button:hover {
  opacity: 0.9;
}

.clip-slot {
  width: 340px;
  height: 240px;
  border-radius: 16px;
}

.clip-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 16px;
}
</style>