v11
This commit is contained in:
@@ -1,125 +1,107 @@
|
||||
<template>
|
||||
<div :class="{'fix-scroll-area-content': hideShadows }" class="fix-scroll-area-content-width column col col-grow">
|
||||
<q-scroll-area
|
||||
ref="scrollAreaRef"
|
||||
:style="{ height: height + 'px' }"
|
||||
class="w100 q-pa-none q-ma-none column col col-grow"
|
||||
@scroll="onScroll"
|
||||
:class=" {
|
||||
'shadow-top': hasScrolled,
|
||||
'shadow-bottom': hasScrolledBottom
|
||||
}"
|
||||
>
|
||||
<slot/>
|
||||
</q-scroll-area>
|
||||
</div>
|
||||
<q-scroll-area
|
||||
ref="scrollAreaRef"
|
||||
:style="scrollStyle"
|
||||
:vertical-offset="[top, bottom]"
|
||||
:thumb-style="{ borderRadius: '3px', background: '#999', width: '6px', opacity: '0.4' }"
|
||||
@scroll="onScroll"
|
||||
class="shadow-scroll w100"
|
||||
>
|
||||
<q-resize-observer @resize="onInnerResize"/>
|
||||
<div :style="{ height: top + 'px'}"/>
|
||||
<slot/>
|
||||
<div :style="{ height: bottom + 'px'}"/>
|
||||
</q-scroll-area>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onActivated, watch } from 'vue'
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
height: number
|
||||
hideShadows: boolean
|
||||
topOffset?: number
|
||||
bottomOffset?: number
|
||||
scrollPosition?: number
|
||||
}>()
|
||||
offset?: number
|
||||
}>(),
|
||||
{
|
||||
scrollPosition: 0,
|
||||
offset: 16
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['updateScrollPosition'])
|
||||
const emit = defineEmits([
|
||||
'updateScrollPosition',
|
||||
'resizeScrollArea'
|
||||
])
|
||||
|
||||
const top = computed(() => props.topOffset || props.offset)
|
||||
const bottom = computed(() => props.bottomOffset || props.offset)
|
||||
const innerHeight = ref(0)
|
||||
|
||||
const hasScrolled = ref(false)
|
||||
const hasScrolledBottom = ref(false)
|
||||
const scrollStyle = computed(() => ({
|
||||
height: props.height + 'px',
|
||||
'--top-height': top.value + 'px',
|
||||
'--bottom-height': bottom.value + 'px'
|
||||
}))
|
||||
|
||||
interface ScrollInfo {
|
||||
verticalPosition: number;
|
||||
verticalPercentage: number;
|
||||
verticalSize: number;
|
||||
verticalContainerSize: number;
|
||||
}
|
||||
|
||||
function onScroll (info: ScrollInfo) {
|
||||
hasScrolled.value = info.verticalPosition > 0
|
||||
const scrollEnd = info.verticalPosition + info.verticalContainerSize >= info.verticalSize - 1
|
||||
hasScrolledBottom.value = !scrollEnd
|
||||
function onScroll () {
|
||||
emit('updateScrollPosition', scrollAreaRef.value?.getScrollPosition().top || 0)
|
||||
}
|
||||
|
||||
const scrollAreaRef = ref()
|
||||
|
||||
onActivated(() => {
|
||||
scrollAreaRef.value?.setScrollPosition('vertical', props?.scrollPosition || 0)
|
||||
})
|
||||
|
||||
watch(() => props.scrollPosition, (newPosition) => {
|
||||
watch(() => props.scrollPosition, async (newPosition) => {
|
||||
await nextTick()
|
||||
scrollAreaRef.value?.setScrollPosition('vertical', newPosition)
|
||||
}, { immediate: true })
|
||||
|
||||
watch([top, bottom, () => props.height], async () => {
|
||||
await nextTick()
|
||||
emit('resizeScrollArea', {
|
||||
height: props.height,
|
||||
innerHeight: innerHeight.value,
|
||||
topOffset: top.value,
|
||||
bottomOffset: bottom.value
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
interface sizeParams {
|
||||
height: number,
|
||||
width: number
|
||||
}
|
||||
|
||||
function onInnerResize (size: sizeParams) {
|
||||
innerHeight.value = size.height
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.q-scrollarea {
|
||||
position: relative;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.q-scrollarea::before {
|
||||
content: '';
|
||||
.shadow-scroll:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(0,0,0,0.12) 0%,
|
||||
rgba(0,0,0,0.08) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
width: 100%;
|
||||
height: var(--top-height);
|
||||
background: linear-gradient(white, transparent);
|
||||
pointer-events: none;
|
||||
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
will-change: opacity, transform;
|
||||
z-index: 1;
|
||||
z-index: 95;
|
||||
}
|
||||
|
||||
.q-scrollarea::after {
|
||||
content: '';
|
||||
.shadow-scroll:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(to top,
|
||||
rgba(0,0,0,0.12) 0%,
|
||||
rgba(0,0,0,0.08) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
will-change: opacity, transform;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fix-scroll-area-content-width:deep(.q-scrollarea__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fix-scroll-area-content:deep(.q-scrollarea::before) {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.fix-scroll-area-content:deep(.q-scrollarea::after) {
|
||||
content: none;
|
||||
height: var(--bottom-height);
|
||||
background: linear-gradient(transparent, white);
|
||||
pointer-events: none;
|
||||
z-index: 95;
|
||||
}
|
||||
|
||||
.q-scrollarea.shadow-top::before {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.q-scrollarea.shadow-bottom::after {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
.shadow-scroll :deep(.q-scrollarea__content) {
|
||||
max-width: min(100vw, var(--body-width));
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user