Article: May 12, 2024
Scroll Animation with Vue 3 and VueUse
Hamza Ahmad @hamzahmd
Animations and Transitions are meant to invoke life on the web pages. Typically, animations occur through user interactions with the website, whether it be clicking a button, hovering over an element, or scrolling through the viewpoint.
There are some specialized animation libraries (like GSAP) on the market, but here we are implementing scroll transition with Vue 3 using composition API and VueUse components which is a lightweight solution. Through this approach, we'll also learn how the specific element is activated when the window scroll reaches that point, opening endless opportunities to perform animations.
Installation
We only need the following three tools:
- vue (v3)
- tailwindcss (optional)
- @vueuse/core
- Let's start with a fresh codebase. You can clone the repository if you prefer. I'm using npm, but you can also use yarn or pnpm.
npm create vite@latest vue-scroll-transition -- --template vue
// or with TypeScript
npm create vite@latest vue-scroll-transition -- --template vue-ts
cd my-project
- Next we need to install tailwind and its peer dependencies. Then generate tailwind.config.js and postcss.config.js files.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Please follow the remaining configuration setups for Tailwind CSS on their official guidelines. They're pretty straightforward.
- Now, install VueUse library. VueUse is a collection of utility functions based on the Composition API.
npm i @vueuse/core
Reusable Component
Now that we've installed all the required dependencies, we can start building our reusable component that we can use anywhere. For the sake of simplicity, let's name it ScrollTransition.vue
:
Basic Example
We're utilizing the useElementVisibility
composable utility. It tracks the visibility of an element within the viewport. For that, we just need to add the ref
to that element. Then, whenever we reach that component, we'll be able to get the true
value; otherwise, the value will be false
. Let's test it with the basic example of opacity (fade-in & fade-out) using Tailwind CSS:
<template>
<div
ref="el"
:class="[
'transition-all duration-500',
isVisible ? 'opacity-100' : 'opacity-0'
]"
>
<slot />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useElementVisibility } from '@vueuse/core'
const el = ref(null)
const isVisible = useElementVisibility(el)
</script>
VueUse is indeed awesome! Since we're using slots, it can take any HTML element or other components.
Single Scroll Animation
Minimize excessive transitions by triggering the animation only once when scrolling reaches the specific element.
<template>
<div
ref="el"
:class="[
'transition-all duration-700',
trueCount < 1 && isVisible ? 'opacity-100' : 'opacity-0'
]"
>
<slot />
</div>
</template>
<script setup>
import { ref, watchEffect, unref } from 'vue'
import { useElementVisibility } from '@vueuse/core'
const el = ref(null)
const isVisible = useElementVisibility(el)
const trueCount = ref(0)
watchEffect(() => {
if (unref(isVisible) === true && unref(trueCount) < 1) {
trueCount.value++
}
})
</script>
Slding Effect
We've reached the point of completing this reusable component. Let's utilize the composition API by defining props for the component.
<template>
<div
ref="el"
:class="[
'transition-all duration-700',
moreDelay ? 'delay-700' : 'delay-300',
trueCount < 1 && translateEffect
? isVisible
? transtionPosition[transition].from
: transtionPosition[transition].to
: '',
trueCount < 1 && opacityEffect
? isVisible
? 'opacity-100'
: 'opacity-0'
: '',
]"
>
<slot />
</div>
</template>
<script setup>
import { ref, watchEffect, unref } from 'vue'
import { useElementVisibility } from '@vueuse/core'
const el = ref(null)
const isVisible = useElementVisibility(el)
const trueCount = ref(0)
watchEffect(() => {
if (unref(isVisible) === true && unref(trueCount) < 1) {
trueCount.value++
}
})
const transtionPosition = = {
vertical: { from: 'translate-y-0', to: 'translate-y-12' },
horizontalToRight: { from: '-translate-x-0', to: '-translate-x-12' },
horizontalToLeft: { from: 'translate-x-0', to: 'translate-x-12' },
}
defineProps([
'transition',
'moreDelay',
'translateEffect',
'opacityEffect'
])
</script>
Here, we can also specify the HTML tag name, such as:
<scroll-transition as='li'/>
With TypeScript
Let's take it to the next level with TypeScript to ensure type safety and enable autocompletion. Here's our final code:
<template>
<component
:is="as"
ref="el"
:class="[
'transition-all duration-700',
moreDelay ? 'delay-700' : 'delay-300',
trueCount < 1 && translateEffect
? isVisible
? transtionPosition[transition].from
: transtionPosition[transition].to
: '',
trueCount < 1 && opacityEffect
? isVisible
? 'opacity-100'
: 'opacity-0'
: '',
]"
>
<slot />
</component>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
import { useElementVisibility } from '@vueuse/core'
type ObjectKeyPair = {
[key: string]: {
[key: string]: string
}
}
const el = ref(null)
const isVisible = useElementVisibility(el)
const trueCount = ref(0)
watchEffect(() => {
if (isVisible.value && trueCount.value < 1) {
trueCount.value++
}
})
const transtionPosition: ObjectKeyPair = {
vertical: { from: 'translate-y-0', to: 'translate-y-12' },
horizontalToRight: { from: '-translate-x-0', to: '-translate-x-12' },
horizontalToLeft: { from: 'translate-x-0', to: 'translate-x-12' },
}
type TransitionTypes = {
translateEffect?: boolean
opacityEffect?: boolean
moreDelay?: boolean
transition?: 'vertical' | 'horizontalToRight' | 'horizontalToLeft'
as?: string
}
withDefaults(defineProps<TransitionTypes>(), {
translateEffect: true,
opacityEffect: true,
moreDelay: false,
transition: 'vertical',
as: 'div',
})
</script>
That's it. Feel free to integrate it into your projects. Here is a quick demo. You can customize it with as many animations as you'd like.
If you find this code snippet useful, please consider giving a star to the code repository.
To ask questions about the article, please comment directly on this Linkedin post.