HumanBreak/src/components/scroll.vue

207 lines
4.9 KiB
Vue
Raw Normal View History

2022-11-14 17:11:23 +08:00
<template>
2022-11-16 23:01:23 +08:00
<div :id="`scroll-div-${id}`" class="scroll-main">
<div class="main-div">
2022-11-14 17:11:23 +08:00
<div :id="`content-${id}`" class="content">
<slot></slot>
</div>
</div>
<canvas :id="`scroll-${id}`" class="scroll"></canvas>
</div>
</template>
<script lang="ts" setup>
2022-11-16 23:01:23 +08:00
import { onMounted, onUnmounted, onUpdated } from 'vue';
2022-11-14 17:11:23 +08:00
import { useDrag, useWheel } from '../plugin/use';
2022-11-16 23:01:23 +08:00
const props = defineProps<{
type?: 'vertical' | 'horizontal';
}>();
2022-11-14 17:11:23 +08:00
let now = 0;
let total = 0;
const id = (1e8 * Math.random()).toFixed(0);
const scale = window.devicePixelRatio;
2022-11-16 23:01:23 +08:00
const cssTarget = props.type === 'horizontal' ? 'left' : 'top';
const canvasAttr = props.type === 'horizontal' ? 'width' : 'height';
2022-11-14 17:11:23 +08:00
let ctx: CanvasRenderingContext2D;
let content: HTMLDivElement;
2022-11-16 23:01:23 +08:00
const resize = () => {
calHeight();
draw();
};
2022-11-14 17:11:23 +08:00
/** 绘制 */
function draw() {
if (total === 0) return;
2022-11-16 23:01:23 +08:00
if (total < ctx.canvas[canvasAttr] / scale) {
now = 0;
} else if (now > total - ctx.canvas[canvasAttr] / scale) {
now = total - ctx.canvas[canvasAttr] / scale;
2022-11-14 17:11:23 +08:00
} else if (now < 0) {
now = 0;
}
const length =
2022-11-16 23:01:23 +08:00
Math.min(ctx.canvas[canvasAttr] / total / scale, 1) *
ctx.canvas[canvasAttr];
const py = (now / total) * ctx.canvas[canvasAttr];
2022-11-14 17:11:23 +08:00
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
2022-11-16 23:01:23 +08:00
if (props.type === 'horizontal') {
ctx.moveTo(Math.max(py + 5, 5), 20 * scale);
ctx.lineTo(Math.min(py + length - 5, ctx.canvas.width - 5), 20 * scale);
} else {
ctx.moveTo(20 * scale, Math.max(py + 5, 5));
ctx.lineTo(
20 * scale,
Math.min(py + length - 5, ctx.canvas.height - 5)
);
}
2022-11-14 17:11:23 +08:00
ctx.lineCap = 'round';
ctx.lineWidth = 6;
ctx.strokeStyle = '#fff';
ctx.stroke();
}
/**
* 计算元素总长度
*/
function calHeight() {
2022-11-16 23:01:23 +08:00
const style = getComputedStyle(content);
total = parseFloat(style[canvasAttr]);
2022-11-14 17:11:23 +08:00
}
function scroll() {
draw();
2022-11-16 23:01:23 +08:00
content.style[cssTarget] = `${-now}px`;
2022-11-14 17:11:23 +08:00
}
onUpdated(() => {
calHeight();
draw();
});
onMounted(() => {
2022-11-16 23:01:23 +08:00
const div = document.getElementById(`scroll-div-${id}`) as HTMLDivElement;
2022-11-14 17:11:23 +08:00
const canvas = document.getElementById(`scroll-${id}`) as HTMLCanvasElement;
2022-11-16 23:01:23 +08:00
const d = document.getElementById(`content-${id}`) as HTMLDivElement;
2022-11-14 17:11:23 +08:00
ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
2022-11-16 23:01:23 +08:00
content = d;
calHeight();
content.addEventListener('resize', resize);
2022-11-14 17:11:23 +08:00
const style = getComputedStyle(canvas);
canvas.width = 40 * scale;
canvas.height = parseFloat(style.height) * scale;
2022-11-16 23:01:23 +08:00
if (props.type === 'horizontal') {
div.style.flexDirection = 'column';
canvas.style.height = '40px';
canvas.style.width = '98%';
canvas.style.margin = '0 1% 0 1%';
canvas.width = parseFloat(style.width) * scale;
canvas.height = 40 * scale;
}
2022-11-14 17:11:23 +08:00
draw();
2022-11-16 23:01:23 +08:00
let last: number;
2022-11-14 17:11:23 +08:00
let contentLast: number;
// 绑定滚动条拖拽事件
useDrag(
canvas,
(x, y) => {
2022-11-16 23:01:23 +08:00
const d = props.type === 'horizontal' ? x : y;
const dy = d - last;
last = d;
if (canvas[canvasAttr] < total * scale)
now += ((dy * total) / canvas[canvasAttr]) * scale;
2022-11-14 17:11:23 +08:00
content.style.transition = '';
scroll();
},
(x, y) => {
2022-11-16 23:01:23 +08:00
last = props.type === 'horizontal' ? x : y;
2022-11-14 17:11:23 +08:00
},
true
);
// 绑定文本拖拽事件
useDrag(
content,
(x, y) => {
2022-11-16 23:01:23 +08:00
const d = props.type === 'horizontal' ? x : y;
const dy = d - contentLast;
contentLast = d;
if (canvas[canvasAttr] < total * scale) now -= dy;
2022-11-14 17:11:23 +08:00
content.style.transition = '';
scroll();
},
(x, y) => {
2022-11-16 23:01:23 +08:00
contentLast = props.type === 'horizontal' ? x : y;
2022-11-14 17:11:23 +08:00
},
true
);
useWheel(content, (x, y) => {
2022-11-16 23:01:23 +08:00
const d = x !== 0 ? x : y;
if (!core.domStyle.isVertical) {
if (Math.abs(d) > 50) {
content.style.transition = `${cssTarget} 0.2s ease-out`;
}
2022-11-14 17:11:23 +08:00
} else {
content.style.transition = '';
}
2022-11-16 23:01:23 +08:00
now += d;
2022-11-14 17:11:23 +08:00
scroll();
});
});
2022-11-16 23:01:23 +08:00
onUnmounted(() => {
content.removeEventListener('resize', resize);
});
2022-11-14 17:11:23 +08:00
</script>
<style lang="less" scoped>
.scroll {
opacity: 0.2;
width: 40px;
height: 98%;
margin-top: 1%;
margin-bottom: 1%;
transition: opacity 0.2s linear;
}
.scroll:hover {
opacity: 0.4;
}
.scroll:active {
opacity: 0.6;
}
2022-11-16 23:01:23 +08:00
.scroll-main {
2022-11-14 17:11:23 +08:00
display: flex;
flex-direction: row;
2022-11-16 23:01:23 +08:00
max-width: 100%;
max-height: 100%;
justify-content: stretch;
2022-11-14 17:11:23 +08:00
}
.content {
2022-11-16 23:01:23 +08:00
width: 100%;
2022-11-14 17:11:23 +08:00
position: relative;
}
2022-11-16 23:01:23 +08:00
.main-div {
flex-basis: 100%;
overflow: hidden;
}
2022-11-14 17:11:23 +08:00
</style>