HumanBreak/src/components/scroll.vue

224 lines
5.2 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-19 11:30:14 +08:00
import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../plugin/use';
2022-11-14 17:11:23 +08:00
2022-11-16 23:01:23 +08:00
const props = defineProps<{
2022-11-19 11:30:14 +08:00
now?: number;
2022-11-16 23:01:23 +08:00
type?: 'vertical' | 'horizontal';
2022-11-19 11:30:14 +08:00
drag?: boolean;
}>();
const emits = defineEmits<{
(e: 'update:now', value: number): void;
(e: 'update:drag', value: boolean): void;
2022-11-16 23:01:23 +08:00
}>();
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;
}
2022-11-19 11:30:14 +08:00
emits('update:now', now);
2022-11-14 17:11:23 +08:00
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') {
2022-11-19 11:30:14 +08:00
ctx.moveTo(Math.max(py + 5, 5), 10 * scale);
ctx.lineTo(Math.min(py + length - 5, ctx.canvas.width - 5), 10 * scale);
2022-11-16 23:01:23 +08:00
} else {
2022-11-19 11:30:14 +08:00
ctx.moveTo(10 * scale, Math.max(py + 5, 5));
2022-11-16 23:01:23 +08:00
ctx.lineTo(
2022-11-19 11:30:14 +08:00
10 * scale,
2022-11-16 23:01:23 +08:00
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(() => {
2022-11-19 11:30:14 +08:00
now = props.now ?? now;
2022-11-14 17:11:23 +08:00
calHeight();
draw();
});
2022-11-19 11:30:14 +08:00
let last: number;
let contentLast: number;
function canvasDrag(x: number, y: number) {
emits('update:drag', true);
const d = props.type === 'horizontal' ? x : y;
const dy = d - last;
last = d;
if (ctx.canvas[canvasAttr] < total * scale)
now += ((dy * total) / ctx.canvas[canvasAttr]) * scale;
content.style.transition = '';
scroll();
}
function contentDrag(x: number, y: number) {
emits('update:drag', true);
const d = props.type === 'horizontal' ? x : y;
const dy = d - contentLast;
contentLast = d;
if (ctx.canvas[canvasAttr] < total * scale) now -= dy;
content.style.transition = '';
scroll();
}
2022-11-14 17:11:23 +08:00
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);
2022-11-19 11:30:14 +08:00
canvas.width = 20 * scale;
2022-11-14 17:11:23 +08:00
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;
2022-11-19 11:30:14 +08:00
canvas.height = 20 * scale;
2022-11-16 23:01:23 +08:00
}
2022-11-14 17:11:23 +08:00
draw();
// 绑定滚动条拖拽事件
useDrag(
canvas,
2022-11-19 11:30:14 +08:00
canvasDrag,
2022-11-14 17:11:23 +08:00
(x, y) => {
2022-11-16 23:01:23 +08:00
last = props.type === 'horizontal' ? x : y;
2022-11-14 17:11:23 +08:00
},
2022-11-19 11:30:14 +08:00
() => {
setTimeout(() => emits('update:drag', false));
},
2022-11-14 17:11:23 +08:00
true
);
// 绑定文本拖拽事件
useDrag(
content,
2022-11-19 11:30:14 +08:00
contentDrag,
2022-11-14 17:11:23 +08:00
(x, y) => {
2022-11-16 23:01:23 +08:00
contentLast = props.type === 'horizontal' ? x : y;
2022-11-14 17:11:23 +08:00
},
2022-11-19 11:30:14 +08:00
() => {
setTimeout(() => emits('update:drag', false));
},
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;
2022-11-19 11:30:14 +08:00
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-19 11:30:14 +08:00
cancelGlobalDrag(canvasDrag);
cancelGlobalDrag(contentDrag);
2022-11-16 23:01:23 +08:00
});
2022-11-14 17:11:23 +08:00
</script>
<style lang="less" scoped>
.scroll {
opacity: 0.2;
2022-11-19 11:30:14 +08:00
width: 20px;
2022-11-14 17:11:23 +08:00
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>