Baserow:Scrollbars
Baserow에서 사용하는 Scrollbars 컴포넌트의 TypeScript 변환 버전.
Code
<!-- Project: Baserow -->
<!-- web-frontend/modules/core/components/Scrollbars.vue -->
<!-- MIT License -->
<template>
<div class="scrollbars">
<div
v-if="vertical !== null && verticalShow"
class="scrollbars__vertical-wrapper"
>
<div
ref="scrollbarVertical"
class="scrollbars__vertical"
:style="{ top: verticalTop + '%', height: verticalHeight + '%' }"
@mousedown="mouseDownVertical($event)"
></div>
</div>
<div
v-if="horizontal !== null && horizontalShow"
class="scrollbars__horizontal-wrapper"
>
<div
ref="scrollbarHorizontal"
class="scrollbars__horizontal"
:style="{ left: horizontalLeft + '%', width: horizontalWidth + '%' }"
@mousedown="mouseDownHorizontal($event)"
></div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Ref, Emit} from 'vue-property-decorator';
import VueBase from '@/base/VueBase';
export function rounder(digits) {
return parseInt('1' + Array(digits + 1).join('0'))
}
export function floor(n, digits = 0) {
const r = rounder(digits)
return Math.floor(n * r) / r
}
export function ceil(n, digits = 0) {
const r = rounder(digits)
return Math.ceil(n * r) / r
}
/**
* This component will render custom scrollbars to a scrollable div. They will
* automatically adjust when you resize the window and can be dragged.
*/
@Component
export default class VirtualScroll extends VueBase {
/**
* The vertical property should be the reference of the vertical scrollable element
* in the parent component.
*/
@Prop()
readonly vertical!: string;
/**
* The horizontal property should be the reference of the vertical scrollable
* element in the parent component.
*/
@Prop()
readonly horizontal!: string;
@Ref()
readonly scrollbarVertical!: HTMLDivElement;
@Ref()
readonly scrollbarHorizontal!: HTMLDivElement;
dragging?: string;
elementStart = 0;
mouseStart = 0;
verticalShow = false;
verticalHeight = 0;
verticalTop = 0;
horizontalShow = false;
horizontalWidth = 0;
horizontalLeft = 0;
mounted() {
this.update();
window.addEventListener('resize', this.onResize);
window.addEventListener('mouseup', this.onMouseUp);
window.addEventListener('mousemove', this.onMouseMove);
}
beforeDestroy() {
window.removeEventListener('resize', this.onResize);
window.removeEventListener('mouseup', this.onMouseUp);
window.removeEventListener('mousemove', this.onMouseMove);
}
onResize() {
this.update();
}
update() {
if (typeof this.vertical !== 'undefined') {
this.updateVertical();
}
if (typeof this.horizontal !== 'undefined') {
this.updateHorizontal();
}
}
/**
* Method that updates the visibility, height and top position of the vertical
* scrollbar handle based on the scrollTop of the vertical scrolling element of the
* parent.
*/
updateVertical() {
const element = this.$parent[this.vertical]();
const show = element.scrollHeight > element.clientHeight;
// @TODO if the client height is very high we have a minimum of 2%, but this needs
// to be subtracted from the top position so that it fits. Same goes for the
// horizontal handler.
const height = Math.max(
floor((element.clientHeight / element.scrollHeight) * 100, 2),
2
);
const top = ceil((element.scrollTop / element.scrollHeight) * 100, 2);
this.verticalShow = show;
this.verticalHeight = height;
this.verticalTop = top;
}
/**
* Method that updates the visibility, width and left position of the horizontal
* scrollbar handle based on the scrollLeft of horizontal scrolling element of the
* parent.
*/
updateHorizontal() {
const element = this.$parent[this.horizontal]();
const show = element.scrollWidth > element.clientWidth;
const width = Math.max(
floor((element.clientWidth / element.scrollWidth) * 100, 2),
2
);
const left = ceil((element.scrollLeft / element.scrollWidth) * 100, 2);
this.horizontalShow = show;
this.horizontalWidth = width;
this.horizontalLeft = left;
}
/**
* Event that is called when the user clicks on the vertical scrollbar handle. It
* will start the vertical dragging.
*/
mouseDownVertical(event: MouseEvent) {
event.preventDefault();
this.dragging = 'vertical';
this.elementStart = this.scrollbarVertical.offsetTop;
this.mouseStart = event.clientY;
}
/**
* Event that is called when the user clicks on the horizontal scrollbar handle. It
* will start the horizontal dragging.
*/
mouseDownHorizontal(event: MouseEvent) {
event.preventDefault();
this.dragging = 'horizontal';
this.elementStart = this.scrollbarHorizontal.offsetLeft;
this.mouseStart = event.clientX;
}
/**
* Event that is called when the mouse moves. If vertical of horizontal scrollbar
* handle is in a dragging state it will emit an event with the new scrollTop.
*/
onMouseMove(event: MouseEvent) {
if (this.dragging === 'vertical') {
event.preventDefault();
const element = this.$parent[this.vertical]();
const delta = event.clientY - this.mouseStart;
const pixel = element.scrollHeight / element.clientHeight;
const top = Math.ceil((this.elementStart + delta) * pixel);
this.emitVertical(top);
this.updateVertical();
}
if (this.dragging === 'horizontal') {
event.preventDefault();
const element = this.$parent[this.horizontal]();
const delta = event.clientX - this.mouseStart;
const pixel = element.scrollWidth / element.clientWidth;
const left = Math.ceil((this.elementStart + delta) * pixel);
this.emitHorizontal(left);
this.updateHorizontal();
}
}
onMouseUp() {
if (typeof this.dragging === 'undefined') {
return;
}
this.dragging = undefined;
this.elementStart = 0;
this.mouseStart = 0;
}
@Emit('vertical')
emitVertical(top: number) {
return top;
}
@Emit('horizontal')
emitHorizontal(left: number) {
return left;
}
}
</script>
<style lang="scss" scoped>
@mixin absolute($top: null, $right: null, $bottom: null, $left: null) {
position: absolute;
@if ($top != null and $right == null and $bottom == null and $left == null) {
top: $top;
right: $top;
bottom: $top;
left: $top;
}
/* stylelint-disable-next-line at-rule-no-unknown */
@else if ($top != null and $right != null and $bottom == null and
$left == null) {
top: $top;
right: $right;
bottom: $top;
left: $right;
}
/* stylelint-disable-next-line at-rule-no-unknown */
@else {
@if $top != null {
top: $top;
}
@if $right != null {
right: $right;
}
@if $bottom != null {
bottom: $bottom;
}
@if $left != null {
left: $left;
}
}
}
$scrollbar-size: 6px;
$color-neutral-900: #272b30 !default;
.scrollbars {
@include absolute(0);
z-index: 100;
pointer-events: none;
}
.scrollbars__vertical-wrapper {
@include absolute(3px, 3px, 3px, auto);
width: $scrollbar-size;
}
.scrollbars__horizontal-wrapper {
@include absolute(auto, 3px, 3px, 3px);
height: $scrollbar-size;
}
%scrollbar {
background-color: $color-neutral-900;
opacity: 0.6;
border-radius: 3px;
pointer-events: auto;
cursor: pointer;
user-select: none;
}
.scrollbars__vertical {
@extend %scrollbar;
@include absolute(auto, 0, auto, 0);
}
.scrollbars__horizontal {
@extend %scrollbar;
@include absolute(0, auto, 0, auto);
}
</style>