Skip to content

BeforeAfter Comparison Slider

Neobrutalist-style image sliding comparison component. Used to visually compare the difference between the same image before (Before) and after (After) modification. Built on native <input type="range"> combined with CSS clip-path masking, zero dependencies, touch-friendly.

Demo

Preview

水平方向

之前
之后

垂直方向

之前
之后

提示:拖拽滑块的位置过渡尊重系统的「减少动态效果」设置。启用后分割线与裁剪区域将即时跟随,不再带有缓动动画。

Installation

pnpm dlx brutx-vue@latest add before-after

Usage

vue
<script setup>
import { BeforeAfter } from 'brutx-ui-vue'

const original = '/images/before.jpg'
const modified = '/images/after.jpg'
</script>

<template>
    <BeforeAfter
        :before="original"
        :after="modified"
        :default-value="50"
    />
</template>

Vertical Orientation

Switch the divider direction via the orientation prop. Default horizontal is left-right dragging; when set to vertical, the divider becomes horizontal with up-down dragging, clipping proceeds from bottom to top (clip-path: inset(0 0 ...)), and the handle icon rotates 90 degrees automatically.

vue
<BeforeAfter
    :before="original"
    :after="modified"
    orientation="vertical"
    :default-value="50"
/>

Browser compatibility note: In vertical mode, the divider/handle position (top: <value>%, larger value = lower) matches the native vertical range's value direction in Chromium; WebKit (Safari) may render the vertical slider's increment direction reversed (dragging down decreases the value and moves the divider up), and the orient attribute is unsupported in some browsers. Verify in target browsers for cross-browser scenarios.

Variants

VariantDescription
horizontalDefault direction, left-right drag divider
verticalVertical direction, up-down drag divider, clipping proceeds from bottom to top

Props

PropTypeDefaultDescription
beforestringURL of the left/bottom original image (required)
afterstringURL of the right/top comparison image (required)
beforeAltstringlocale: beforeAfter.beforealt attribute for the original image
afterAltstringlocale: beforeAfter.afteralt attribute for the comparison image
modelValuenumberDivider position (v-model, 0-100)
defaultValuenumber50Initial percentage position of the divider (0-100)
disabledbooleanfalseWhether to disable drag interaction
orientation'horizontal' | 'vertical''horizontal'Divider direction; vertical clips from bottom to top
iconSize'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl''default'Size of the drag handle icon
classstring""Custom CSS class for the container

Events

EventPayloadDescription
update:modelValuenumberDivider position changed

Accessibility

  • Keyboard control: Supports keyboard arrow keys to control divider position; keyboard interaction is disabled in disabled state
  • Focus indicator: The transparent range input has no visible focus by itself; keyboard focus state is surfaced on the container ring via the has-[:focus-visible] variant (ring-brutal-ring), so the current position is perceivable when tabbing
  • Motion reduction: The component respects the prefers-reduced-motion system setting. When the user enables "reduce motion" (via useReducedMotion listening for prefers-reduced-motion: reduce):
    • Slider transition removal: The divider, drag handle, and clip-path clipping layer no longer apply transition styles. Position changes take effect immediately during dragging without easing animations
    • Interaction preserved: Dragging, keyboard arrow keys, disabled and other behaviors are completely unaffected; only visual transitions are removed
    • Real-time response: Preference changes take effect immediately without remounting the component
    • This mechanism is implemented through a reactive motionTransition computed property: in motion reduction mode it returns an empty string, causing related elements to skip transition styles

FAQ

Q: What is the technical approach of this component?

A: This component is built on the native browser <input type="range"> combined with CSS clip-path masking, with the following advantages:

  1. Touch-friendly: Perfectly adapts to swipe gestures on mobile phones, tablets, and other mobile devices
  2. Zero dependencies: No need to load any additional external gesture interaction JS libraries, package size is nearly zero
  3. Perfect layout: The underlying clip-path: inset(...) clipping method does not break the image aspect ratio, ensuring alignment across platforms
  4. Adaptive ratio: The container automatically calculates aspect-ratio based on the actual image dimensions, no need to manually specify the ratio. Before images load, default ratios are used as placeholders (horizontal 16:9, vertical 9:16)

Brute force builds.