APIs
Scrollbar
参数 说明 类型 默认值 必传 contentStyle 内容样式 CSSProperties {} false size 滚动条的大小,单位 px
number 5 false trigger 显示滚动条的时机,'none'
表示一直显示 ‘hover’ | ‘none’ ‘hover’ false horizontal 是否使用横向滚动 boolean false false
Methods
名称 说明 类型 scrollTo 滚动内容 (options: { left?: number, top?: number, behavior?: ScrollBehavior }): void & (x: number, y: number) => void scrollBy 滚动特定距离 (options: { left?: number, top?: number, behavior?: ScrollBehavior }): void & (x: number, y: number) => void
ScrollBehavior Type
名称 说明 smooth 平滑滚动并产生过渡效果 instant 滚动会直接跳转到目标位置,没有过渡效果 auto 或缺省值表示浏览器会自动选择滚动时的过渡效果
Events
名称 说明 类型 scroll 滚动的回调 (e: Event) => void
创建滚动条组件Scrollbar.vue
< script lang= "ts" >
export default {
inheritAttrs: false
}
< / script>
< script setup lang= "ts" >
import { ref, computed, onMounted } from 'vue'
import type { CSSProperties } from 'vue'
import { useEventListener, useMutationObserver } from '../utils'
interface Props {
contentStyle? : CSSProperties
size? : number
trigger? : 'hover' | 'none'
horizontal? : boolean
}
const props = withDefaults ( defineProps < Props> ( ) , {
contentStyle : ( ) => ( { } ) ,
size: 5 ,
trigger: 'hover' ,
horizontal: false
} )
const containerRef = ref ( )
const contentRef = ref ( )
const railVerticalRef = ref ( )
const railHorizontalRef = ref ( )
const showTrack = ref ( false )
const containerScrollHeight = ref ( 0 )
const containerScrollWidth = ref ( 0 )
const containerClientHeight = ref ( 0 )
const containerClientWidth = ref ( 0 )
const containerHeight = ref ( 0 )
const containerWidth = ref ( 0 )
const contentHeight = ref ( 0 )
const contentWidth = ref ( 0 )
const railHeight = ref ( 0 )
const railWidth = ref ( 0 )
const containerScrollTop = ref ( 0 )
const containerScrollLeft = ref ( 0 )
const trackYPressed = ref ( false )
const trackXPressed = ref ( false )
const mouseLeave = ref ( false )
const memoYTop = ref < number > ( 0 )
const memoXLeft = ref < number > ( 0 )
const memoMouseY = ref < number > ( 0 )
const memoMouseX = ref < number > ( 0 )
const horizontalContentStyle = { width: 'fit-content' }
const emit = defineEmits ( [ 'scroll' ] )
const isYScroll = computed ( ( ) => {
return containerScrollHeight. value > containerClientHeight. value
} )
const isXScroll = computed ( ( ) => {
return containerScrollWidth. value > containerClientWidth. value
} )
const isScroll = computed ( ( ) => {
return isYScroll. value || ( props. horizontal && isXScroll. value)
} )
const trackHeight = computed ( ( ) => {
if ( isYScroll. value) {
if ( containerHeight. value && contentHeight. value && railHeight. value) {
const value = Math. min (
containerHeight. value,
( railHeight. value * containerHeight. value) / contentHeight. value + 1.5 * props. size
)
return Number ( value. toFixed ( 4 ) )
}
}
return 0
} )
const trackTop = computed ( ( ) => {
if ( containerHeight. value && contentHeight. value && railHeight. value) {
return (
( containerScrollTop. value / ( contentHeight. value - containerHeight. value) ) *
( railHeight. value - trackHeight. value)
)
}
return 0
} )
const trackWidth = computed ( ( ) => {
if ( props. horizontal && isXScroll. value) {
if ( containerWidth. value && contentWidth. value && railWidth. value) {
const value = ( railWidth. value * containerWidth. value) / contentWidth. value + 1.5 * props. size
return Number ( value. toFixed ( 4 ) )
}
}
return 0
} )
const trackLeft = computed ( ( ) => {
if ( containerWidth. value && contentWidth. value && railWidth. value) {
return (
( containerScrollLeft. value / ( contentWidth. value - containerWidth. value) ) * ( railWidth. value - trackWidth. value)
)
}
return 0
} )
onMounted ( ( ) => {
updateState ( )
} )
function updateScrollState ( ) {
containerScrollTop. value = containerRef. value. scrollTop
containerScrollLeft. value = containerRef. value. scrollLeft
}
function updateScrollbarState ( ) {
containerScrollHeight. value = containerRef. value. scrollHeight
containerScrollWidth. value = containerRef. value. scrollWidth
containerClientHeight. value = containerRef. value. clientHeight
containerClientWidth. value = containerRef. value. clientWidth
containerHeight. value = containerRef. value. offsetHeight
containerWidth. value = containerRef. value. offsetWidth
contentHeight. value = contentRef. value. offsetHeight
contentWidth. value = contentRef. value. offsetWidth
railHeight. value = railVerticalRef. value. offsetHeight
railWidth. value = railHorizontalRef. value. offsetWidth
}
function updateState ( ) {
updateScrollState ( )
updateScrollbarState ( )
}
useEventListener ( window, 'resize' , updateState)
const options = { childList: true , attributes: true , subtree: true }
useMutationObserver ( containerRef, updateState, options)
function onScroll ( e: Event) {
emit ( 'scroll' , e)
updateScrollState ( )
}
function onMouseEnter ( ) {
if ( props. horizontal) {
if ( trackXPressed. value) {
mouseLeave. value = false
} else {
showTrack. value = true
}
} else {
if ( trackYPressed. value) {
mouseLeave. value = false
} else {
showTrack. value = true
}
}
}
function onMouseLeave ( ) {
if ( props. horizontal) {
if ( trackXPressed. value) {
mouseLeave. value = true
} else {
showTrack. value = false
}
} else {
if ( trackYPressed. value) {
mouseLeave. value = true
} else {
showTrack. value = false
}
}
}
function onTrackVerticalMouseDown ( e: MouseEvent) {
trackYPressed. value = true
memoYTop. value = containerScrollTop. value
memoMouseY. value = e. clientY
window. onmousemove = ( e: MouseEvent) => {
const diffY = e. clientY - memoMouseY. value
const dScrollTop =
( diffY * ( contentHeight. value - containerHeight. value) ) / ( containerHeight. value - trackHeight. value)
const toScrollTopUpperBound = contentHeight. value - containerHeight. value
let toScrollTop = memoYTop. value + dScrollTop
toScrollTop = Math. min ( toScrollTopUpperBound, toScrollTop)
toScrollTop = Math. max ( toScrollTop, 0 )
containerRef. value. scrollTop = toScrollTop
}
window. onmouseup = ( ) => {
window. onmousemove = null
trackYPressed. value = false
if ( props. trigger === 'hover' && mouseLeave. value) {
showTrack. value = false
mouseLeave. value = false
}
}
}
function onTrackHorizontalMouseDown ( e: MouseEvent) {
trackXPressed. value = true
memoXLeft. value = containerScrollLeft. value
memoMouseX. value = e. clientX
window. onmousemove = ( e: MouseEvent) => {
const diffX = e. clientX - memoMouseX. value
const dScrollLeft =
( diffX * ( contentWidth. value - containerWidth. value) ) / ( containerWidth. value - trackWidth. value)
const toScrollLeftUpperBound = contentWidth. value - containerWidth. value
let toScrollLeft = memoXLeft. value + dScrollLeft
toScrollLeft = Math. min ( toScrollLeftUpperBound, toScrollLeft)
toScrollLeft = Math. max ( toScrollLeft, 0 )
containerRef. value. scrollLeft = toScrollLeft
}
window. onmouseup = ( ) => {
window. onmousemove = null
trackXPressed. value = false
if ( props. trigger === 'hover' && mouseLeave. value) {
showTrack. value = false
mouseLeave. value = false
}
}
}
function scrollTo ( ... args: any [ ] ) {
containerRef. value?. scrollTo ( ... args)
}
function scrollBy ( ... args: any [ ] ) {
containerRef. value?. scrollBy ( ... args)
}
defineExpose ( {
scrollTo,
scrollBy
} )
< / script>
< template>
< div
class = "m-scrollbar"
: style= "`--scrollbar-size: ${size}px;`"
@ mouseenter = "isScroll && trigger === 'hover' ? onMouseEnter() : () => false"
@ mouseleave = "isScroll && trigger === 'hover' ? onMouseLeave() : () => false"
>
< div ref= "containerRef" class = "m-scrollbar-container" @ scroll = "onScroll" v- bind= "$attrs" >
< div
ref= "contentRef"
class = "m-scrollbar-content"
: style= "[horizontal ? { ...horizontalContentStyle, ...contentStyle } : contentStyle]"
>
< slot> < / slot>
< / div>
< / div>
< div ref= "railVerticalRef" class = "m-scrollbar-rail rail-vertical" >
< Transition name= "fade" >
< div
v- if = "trigger === 'none' || showTrack"
class = "m-scrollbar-track"
: style= "`top: ${trackTop}px; height: ${trackHeight}px;`"
@ mousedown . prevent. stop= "onTrackVerticalMouseDown"
> < / div>
< / Transition>
< / div>
< div ref= "railHorizontalRef" v- show= "horizontal" class = "m-scrollbar-rail rail-horizontal" >
< Transition name= "fade" >
< div
v- if = "trigger === 'none' || showTrack"
class = "m-scrollbar-track"
: style= "`left: ${trackLeft}px; width: ${trackWidth}px;`"
@ mousedown . prevent. stop= "onTrackHorizontalMouseDown"
> < / div>
< / Transition>
< / div>
< / div>
< / template>
< style lang= "less" scoped>
. fade- enter- active,
. fade- leave- active {
transition: all 0 . 2s cubic- bezier ( 0.4 , 0 , 0.2 , 1 ) ! important;
}
. fade- enter- from,
. fade- leave- to {
opacity: 0 ;
}
. m- scrollbar {
overflow: hidden;
position: relative;
z- index: auto;
height: 100 % ;
width: 100 % ;
. m- scrollbar- container {
width: 100 % ;
overflow: scroll;
height: 100 % ;
min- height: inherit;
max- height: inherit;
scrollbar- width: none;
& : : - webkit- scrollbar,
& : : - webkit- scrollbar- track- piece,
& : : - webkit- scrollbar- thumb {
width: 0 ;
height: 0 ;
display: none;
}
. m- scrollbar- content {
box- sizing: border- box;
min- width: 100 % ;
}
}
. m- scrollbar- rail {
position: absolute;
pointer- events: none;
user- select: none;
background: transparent;
- webkit- user- select: none;
. m- scrollbar- track {
z- index: 1 ;
position: absolute;
cursor: pointer;
pointer- events: all;
background- color: rgba ( 0 , 0 , 0 , 0.25 ) ;
transition: background- color 0 . 2s cubic- bezier ( 0.4 , 0 , 0.2 , 1 ) ;
& : hover {
background- color: rgba ( 0 , 0 , 0 , 0.4 ) ;
}
}
}
. rail- vertical {
inset: 2px 4px 2px auto;
width: var ( -- scrollbar- size) ;
. m- scrollbar- track {
width: var ( -- scrollbar- size) ;
border- radius: var ( -- scrollbar- size) ;
bottom: 0 ;
}
}
. rail- horizontal {
inset: auto 2px 4px 2px;
height: var ( -- scrollbar- size) ;
. m- scrollbar- track {
height: var ( -- scrollbar- size) ;
border- radius: var ( -- scrollbar- size) ;
right: 0 ;
}
}
}
< / style>
在要使用的页面引入
< script setup lang= "ts" >
import Scrollbar from './Scrollbar.vue'
function onScroll ( e: Event) {
console . log ( 'scroll:' , e)
}
< / script>
< template>
< div>
< h1> { { $route. name } } { { $route. meta. title } } < / h1>
< h2 class = "mt30 mb10" > 基本使用< / h2>
< Scrollbar style= "max-height: 120px" @ scroll = "onScroll" >
我们在田野上面找猪< br / >
想象中已找到了三只< br / >
小鸟在白云上面追逐< br / >
它们在树底下跳舞< br / >
啦啦啦啦啦啦啦啦咧< br / >
啦啦啦啦咧< br / >
我们在想象中度过了许多年< br / >
想象中我们是如此的疯狂< br / >
我们在城市里面找猪< br / >
想象中已找到了几百万只< br / >
小鸟在公园里面唱歌< br / >
它们独自在想象里跳舞< br / >
啦啦啦啦啦啦啦啦咧< br / >
啦啦啦啦咧< br / >
我们在想象中度过了许多年< br / >
许多年之后我们又开始想象< br / >
啦啦啦啦啦啦啦啦咧
< / Scrollbar>
< h2 class = "mt30 mb10" > 横向滚动< / h2>
< Scrollbar horizontal>
< div style= "white-space: nowrap; padding: 12px" >
我们在田野上面找猪 想象中已找到了三只 小鸟在白云上面追逐 它们在树底下跳舞 啦啦啦啦啦啦啦啦咧 啦啦啦啦咧
我们在想象中度过了许多年 想象中我们是如此的疯狂 我们在城市里面找猪 想象中已找到了几百万只 小鸟在公园里面唱歌
它们独自在想象里跳舞 啦啦啦啦啦啦啦啦咧 啦啦啦啦咧 我们在想象中度过了许多年 许多年之后我们又开始想象
啦啦啦啦啦啦啦啦咧
< / div>
< / Scrollbar>
< h2 class = "mt30 mb10" > 触发方式< / h2>
< Scrollbar horizontal style= "max-height: 120px" trigger= "none" >
我们在田野上面找猪< br / >
想象中已找到了三只< br / >
小鸟在白云上面追逐< br / >
它们在树底下跳舞< br / >
啦啦啦啦啦啦啦啦咧< br / >
啦啦啦啦咧< br / >
我们在想象中度过了许多年< br / >
想象中我们是如此的疯狂< br / >
我们在城市里面找猪< br / >
想象中已找到了几百万只< br / >
小鸟在公园里面唱歌< br / >
它们独自在想象里跳舞< br / >
啦啦啦啦啦啦啦啦咧< br / >
啦啦啦啦咧< br / >
我们在想象中度过了许多年< br / >
许多年之后我们又开始想象< br / >
啦啦啦啦啦啦啦啦咧
< / Scrollbar>
< h2 class = "mt30 mb10" > 自定义内容样式< / h2>
< Scrollbar
style= "max-height: 120px; border-radius: 12px"
: content- style= "{ backgroundColor: '#e6f4ff', padding: '16px 24px', fontSize: '16px' }"
>
我们在田野上面找猪< br / >
想象中已找到了三只< br / >
小鸟在白云上面追逐< br / >
它们在树底下跳舞< br / >
啦啦啦啦啦啦啦啦咧< br / >
啦啦啦啦咧< br / >
我们在想象中度过了许多年< br / >
想象中我们是如此的疯狂< br / >
我们在城市里面找猪< br / >
想象中已找到了几百万只< br / >
小鸟在公园里面唱歌< br / >
它们独自在想象里跳舞< br / >
啦啦啦啦啦啦啦啦咧< br / >
啦啦啦啦咧< br / >
我们在想象中度过了许多年< br / >
许多年之后我们又开始想象< br / >
啦啦啦啦啦啦啦啦咧
< / Scrollbar>
< / div>
< / template>