Skip to content

Animated Beam

An animated beam of light which travels along a path. Useful for showcasing the "integration" features of a website.

Installation

Copy and paste the following code into your project:

vue
<script setup lang="ts">
import { onMounted, onUnmounted, ref, useId, watch } from 'vue'

const props = withDefaults(defineProps<{
  containerRef: any
  fromRef: any
  toRef: any
  className?: string
  curvature?: number
  reverse?: boolean
  pathColor?: string
  pathWidth?: number
  pathOpacity?: number
  gradientStartColor?: string
  gradientStopColor?: string
  delay?: number
  duration?: number
  startXOffset?: number
  startYOffset?: number
  endXOffset?: number
  endYOffset?: number
  width?: number
  height?: number
}>(), {
  curvature: 0,
  reverse: false,
  duration: Math.random() * 3 + 4,
  delay: 3,
  pathColor: 'gray',
  pathWidth: 2,
  pathOpacity: 0.2,
  gradientStartColor: '#ffaa40',
  gradientStopColor: '#9c40ff',
  startXOffset: 0,
  startYOffset: 0,
  endXOffset: 0,
  endYOffset: 0,
})

const id = `pattern-${useId()}`

const initial = {
  x1: '10%',
  x2: '0%',
  y1: '0%',
  y2: '0%',
}
const svgDimensions = ref({ width: 0, height: 0 })
const pathD = ref('')
function updatePath() {
  if (
    props.containerRef
    && props.fromRef?.circleRef
    && props.toRef?.circleRef
  ) {
    const containerRect = props.containerRef?.getBoundingClientRect()
    const rectA = props.fromRef?.circleRef?.getBoundingClientRect()
    const rectB = props.toRef?.circleRef?.getBoundingClientRect()

    const svgWidth = containerRect?.width
    const svgHeight = containerRect?.height
    svgDimensions.value.width = svgWidth
    svgDimensions.value.height = svgHeight

    const startX = rectA.left - containerRect.left + rectA.width / 2 + props.startXOffset
    const startY = rectA.top - containerRect.top + rectA.height / 2 + props.startYOffset
    const endX = rectB.left - containerRect.left + rectB.width / 2 + props.endXOffset
    const endY = rectB.top - containerRect.top + rectB.height / 2 + props.endYOffset

    const controlY = startY - props.curvature
    const d = `M ${startX},${startY} Q ${(startX + endX) / 2
    },${controlY} ${endX},${endY}`
    pathD.value = d
  }
}

const controller = new AbortController()

onMounted(() => {
  window.addEventListener('resize', updatePath, {
    signal: controller.signal,
  })
})

onUnmounted(() => {
  controller.abort()
})

watch(props, (_) => {
  updatePath()
}, { deep: true })
</script>

<template>
  <svg
    :width="svgDimensions?.width" :height="svgDimensions?.height" xmlns="http://www.w3.org/2000/svg" class="pointer-events-none absolute left-0 top-0 transform-gpu stroke-2" :class="[
      props.className,
    ]" :viewBox="`0 0 ${svgDimensions?.width} ${svgDimensions?.height}`"
  >
    <path
      :d="pathD" :stroke="pathColor" fill="none" :stroke-width="pathWidth" :stroke-opacity="pathOpacity"
      stroke-linecap="round"
    />
    <path :d="pathD" :stroke="`url(#${id}`" fill="none" stroke-linecap="round" />
    <defs>
      <linearGradient
        :id="id" v-motion :initial="{
          opacity: 0,
          x: props.reverse ? ['100%', '0%'] : ['0%', '100%'],
        }" :enter="{
          opacity: 1,
          x: props.reverse ? ['100%', '0%'] : ['0%', '100%'],
          transition: {
            duration: 1600,
            type: 'keyframes',
            easings: [0.16, 1, 0.3, 1],
            repeat: Infinity,
          },
        }" gradientUnits="userSpaceOnUse" :x1="initial.x1" :x2="initial.x2" :y1="initial.y1"
        :y2="initial.y2"
      >
        <stop :stop-color="props.gradientStartColor" stop-opacity="0" />
        <stop :stop-color="props.gradientStartColor" />
        <stop offset="32.5%" :stop-color="props.gradientStopColor" />
        <stop offset="100%" :stop-color="props.gradientStopColor" stop-opacity="0" />
      </linearGradient>
    </defs>
  </svg>
</template>
vue
<script setup lang='ts'>
import { ref } from 'vue'

const circleRef = ref()

defineExpose({
  circleRef,
})
</script>

<template>
  <div
    ref="circleRef"
    class="z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]"
  >
    <slot />
  </div>
</template>

Examples

Animated Beam Uni-Directional

Animated Beam Bi-Directional

Animated Beam Multiple Inputs

Animated Beam Multiple Outputs

Props

Animated Beam

PropTypeDescriptionDefault
classstringThe class for the component.-
containerRefrefThe container ref.-
fromRefrefThe ref of the element from which the beam should start.-
toRefrefThe ref of the element to which the beam should end.-
curvaturenumberThe curvature of the beam.0
reversebooleanWhether the beam should be reversed.false
durationnumberThe duration of the beam.5
delaynumberThe delay of the beam.0
pathColorstringThe color of the beam."gray"
pathWidthnumberThe width of the beam.2
pathOpacitynumberThe opacity of the beam.0.2
gradientStartColorstringThe start color of the gradient."#ffaa40"
gradientStopColorstringThe stop color of the gradient."#9c40ff"
startXOffsetnumberThe start x offset of the beam.0
startYOffsetnumberThe start y offset of the beam.0
endXOffsetnumberThe end x offset of the beam.0
endYOffsetnumberThe end y offset of the beam.0

Released under the MIT License.