Skip to content

Sepush/vue-scroll-area

Repository files navigation

Vue Scroll Area

A Vue 3 component for a scroll area with DOM-level custom scrollbars.

Credit

The idea and original implementation come from @07akioni.

Original repo: react-scroll-area-demo

This project ports the idea to Vue 3.

Installation

pnpm i vue-scroll-area

Usage

Basic

<template>
  <ScrollArea
    :style="{ height: '240px', width: '480px' }"
    :vertical-scrollbar-min-height="20"
    :horizontal-scrollbar-min-width="20"
  >
    <div style="width: 960px; min-height: 640px;">
      Your scrollable content goes here.
    </div>
  </ScrollArea>
</template>

<script setup lang="ts">
import { ScrollArea } from 'vue-scroll-area'
</script>

Resizable Content

Use ScrollAreaResizeableContent when the content size can change after mount.

<template>
  <ScrollArea :style="{ height: '240px', width: '480px' }">
    <ScrollAreaResizeableContent>
      <div style="width: 960px;">
        Resizable content
      </div>
    </ScrollAreaResizeableContent>
  </ScrollArea>
</template>

<script setup lang="ts">
import { ScrollArea, ScrollAreaResizeableContent } from 'vue-scroll-area'
</script>

Textarea

For a native textarea, render a custom container and set gutters-position="absolute" on ScrollArea. The custom container owns the textarea element and places ScrollAreaGutters inside the same positioned wrapper.

<!-- TextareaScroll.vue -->
<script setup lang="ts">
import { h } from 'vue'
import type { ScrollAreaContainerProps, ScrollAreaContainerRender } from 'vue-scroll-area'
import { ScrollArea } from 'vue-scroll-area'
import TextareaContainer from './TextareaContainer.vue'

const textareaContainerRenderer: ScrollAreaContainerRender = (props: ScrollAreaContainerProps) => {
  return h(TextareaContainer, props)
}
</script>

<template>
  <ScrollArea :container-render="textareaContainerRenderer" gutters-position="absolute" />
</template>
<!-- TextareaContainer.vue -->
<template>
  <div class="textarea-shell">
    <ScrollAreaGutters />
    <textarea
      :class="props.scrollAreaClass"
      :ref="(el: any) => { props.containerDomRef.value = el }"
      v-model="value"
      wrap="off"
      @input="props.onContentResize"
      @scroll="props.onContainerScroll"
    />
    <div ref="mirrorRef" class="textarea-mirror">
      {{ value }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import type { ScrollAreaContainerProps } from 'vue-scroll-area'
import { ScrollAreaGutters } from 'vue-scroll-area'

const props = defineProps<ScrollAreaContainerProps>()

const value = ref(`Hello world
This textarea uses custom scrollbars.
This is a long line that can trigger horizontal scrolling when wrap is off.`)
const mirrorRef = ref<HTMLDivElement | null>(null)
let resizeObserver: ResizeObserver | null = null

onMounted(() => {
  resizeObserver = new ResizeObserver(() => {
    props.onContentResize()
    props.onContainerResize()
  })

  if (props.containerDomRef.value) {
    resizeObserver.observe(props.containerDomRef.value)
  }
  if (mirrorRef.value) {
    resizeObserver.observe(mirrorRef.value)
  }
})

onUnmounted(() => {
  resizeObserver?.disconnect()
})
</script>

<style scoped>
.textarea-shell {
  position: relative;
  display: flex;
  width: 320px;
  height: 200px;
  overflow: hidden;
  resize: both;
}

textarea {
  box-sizing: border-box;
  width: 100%;
  padding: 12px;
  border: 0;
  background: transparent;
  font: 14px/1.6 system-ui;
}

.textarea-mirror {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  width: 100%;
  padding: 12px;
  visibility: hidden;
  white-space: pre;
  pointer-events: none;
  font: 14px/1.6 system-ui;
}
</style>

Props

Prop Type Default Description
style CSSProperties undefined Styles applied to the default scroll container.
containerRender ScrollAreaContainerRender undefined Custom container renderer for native elements such as textarea.
guttersPosition 'sticky' | 'absolute' 'sticky' Positioning strategy for scrollbar gutters. Use absolute for custom containers that hide overflow.
verticalScrollbarMinHeight number 0 Minimum vertical scrollbar thumb height.
horizontalScrollbarMinWidth number 0 Minimum horizontal scrollbar thumb width.
verticalScrollbarWidth number 8 Vertical gutter width.
horizontalScrollbarHeight number 8 Horizontal gutter height.
verticalScrollbarInsets DirectionInsets { leading: 0, trailing: 8, top: 8, bottom: 8 } Insets for the vertical scrollbar.
horizontalScrollbarInsets DirectionInsets { leading: 8, trailing: 8, top: 0, bottom: 8 } Insets for the horizontal scrollbar.
conflictedVerticalScrollbarInsets DirectionInsets { leading: 0, trailing: 8, top: 8, bottom: 16 } Vertical insets when both scrollbars are visible.
conflictedHorizontalScrollbarInsets DirectionInsets { leading: 8, trailing: 16, top: 0, bottom: 8 } Horizontal insets when both scrollbars are visible.
type DirectionInsets = {
  leading: number
  trailing: number
  top: number
  bottom: number
}

Development Setup

corepack prepare
pnpm i
pnpm dev

If the playground does not pick up library type or build changes, run:

pnpm build

About

A scroll area with DOM-level custom scrollbar

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors