NuxtLegoNuxtLego

Magnified Dock

Recreation of the magnification effect from the Mac OS dock, inspired by Build UI.

Althought Vue doesn't have the popular framer-motion library, but we by leveraging vueuse, and vueuse/motion, we can achieve the same effect just as well.

We need to create 2 component,

First, a Dock component that would hold all the AppIcon.

Dock.vue
<script setup lang="ts">
import AppIcon from './AppIcon.vue'

const mouseX = ref(Infinity)
</script>

<template>
  <div
    class=" flex h-16 items-end gap-4 rounded-2xl  bg-white bg-opacity-20 backdrop-blur-md px-4 pb-3"
    @mousemove="mouseX = $event.pageX"
    @mouseleave="mouseX = Infinity"
  >
    <AppIcon v-for="i in 8" :key="i" :mouse-x="mouseX" />
  </div>
</template>

Secondly, we will create the AppIcon that will react to the mouseX position and adjust the size.

AppIcon.vue
<script setup lang="ts">
import { useMotionProperties, useMotionTransitions } from '@vueuse/motion'

const props = defineProps<{
  mouseX: number
}>()
const { mouseX } = toRefs(props)

const target = ref<HTMLElement>()

const { motionProperties } = useMotionProperties(target, { width: 40 })
const { push } = useMotionTransitions()

// relative distance of target element to mouseX
const distance = computed(() => {
  const bounds = target.value?.getBoundingClientRect() ?? { x: 0, width: 0 }

  const val = Math.abs(mouseX.value - bounds.x - bounds.width / 2)
  return val > 150 ? 150 : val
})
const widthSync = useProjection(distance, [0, 150], [100, 40])

watch(widthSync, () => {
  push('width', widthSync.value, motionProperties, { type: 'spring', mass: 0.1, stiffness: 150, damping: 12 })
})
</script>

<template>
  <div ref="target" class="aspect-square w-10 rounded-full bg-white" />
</template>
cd ..