背景
实现一个可以在浏览器窗口右侧浮动的按钮工具。可以支持鼠标拖拽上下移动,保持贴边在窗口右侧。
使用
<div ref="toolsRef"></div>
...
import { useDrag } from './hooks/use-drag';
...
const toolsRef = ref<HTMLElement | null>(null);
useDrag(toolsRef);
组件全部代码
<template>
<div class="ad-components-audit-tools" v-show="toolsVisible" ref="toolsRef">
<div class="title">
审核小工具
</div>
<ks-tooltip size="mini" class="item" effect="dark" content="关闭后,刷新页面可以再次出现" placement="top">
<i class="sys-icon-close-circle-fill" @click="close"></i>
</ks-tooltip>
<ks-divider></ks-divider>
<div class="tool-box">
<html-preview />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, isVue3, CreateAppFunction } from 'vue-demi';
import HtmlPreview from './components/html-preview/index.vue';
import { useDrag } from './hooks/use-drag';
const com = defineComponent({
name: 'audit-tools',
components: {
HtmlPreview
},
props: {},
setup() {
const toolsVisible = ref(true);
const toolsRef = ref<HTMLElement | null>(null);
const close = function () {
toolsVisible.value = false;
};
useDrag(toolsRef);
return {
toolsRef,
toolsVisible,
close
};
}
});
// 附加挂载函数,区分 vue2 和 vue3
com._$mountTools = () => {
if (isVue3) {
return (createApp: CreateAppFunction<any>, KwaiUI: any) => {
const app = createApp(com);
app.use(KwaiUI);
const el = document.createElement('div');
document.body.appendChild(el);
app.mount(el); // 将应用实例挂载到新创建的 div 元素上
}
} else {
// @vueInstance Vue2构造函数
return (vueInstance: any) => {
const el = document.createElement('div');
document.body.appendChild(el);
const AuditToolsConstructor = vueInstance.extend({
render: (h: any) => h(com)
});
new AuditToolsConstructor().$mount(el);
}
}
};
export default com;
</script>
<style lang="less" scoped>
.ad-components-audit-tools {
position: fixed;
top: 200px;
right: 0;
z-index: 9999;
cursor: pointer;
transition: transform .2s;
padding: 10px;
background: #fff;
border-radius: 10px 0 0 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
transform: translateX(calc(100% - 40px));
height: 20px;
overflow: hidden;
.sys-icon-close-circle-fill {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
color: #999;
}
// hover显示,平时默认收起为一个小方块
&:hover {
transform: translateX(0);
height: auto;
}
/deep/ .ks-divider {
margin: 10px 0;
}
.title {
font-size: 16px;
font-weight: 700;
padding-right: 30px;
}
.tool-box {
display: flex;
flex-direction: column;
align-items: center;
}
}
</style>
实现
实现use-drag.ts
import { onMounted, onUnmounted, Ref } from 'vue-demi';
export const useDrag = (toolsRef: Ref) => {
let offsetY = 0;
let isDragging = false;
let maxYPos = 0;
let minYPos = 0;
const startDragging = (event: MouseEvent) => {
isDragging = true;
const toolsElement = toolsRef.value;
if (toolsElement) {
const boundingRect = toolsElement.getBoundingClientRect();
offsetY = event.clientY - boundingRect.top;
// Calculate the maximum and minimum y positions
maxYPos = window.innerHeight - boundingRect.height;
minYPos = 0;
}
};
const handleDragging = (event: MouseEvent) => {
if (isDragging) {
const toolsElement = toolsRef.value;
if (toolsElement) {
const yPos = event.clientY - offsetY;
// Restricting to vertical movement within the boundaries
const constrainedYPos = Math.max(minYPos, Math.min(maxYPos, yPos));
toolsElement.style.top = `${constrainedYPos}px`;
toolsElement.style.bottom = `auto`; // To override any previous 'bottom' positioning
// 存到 localStorage
localStorage.setItem('audit-tools-y', `${constrainedYPos}`);
}
}
};
const stopDragging = () => {
isDragging = false;
};
onMounted(() => {
const toolsElement = toolsRef.value;
if (toolsElement) {
toolsElement.addEventListener('mousedown', startDragging);
document.addEventListener('mousemove', handleDragging);
document.addEventListener('mouseup', stopDragging);
// 从 localStorage 中取出 不要超过屏幕高度
const y = localStorage.getItem('audit-tools-y');
if (y) {
const yNum = Number(y);
if (yNum < window.innerHeight) {
toolsElement.style.top = `${yNum}px`;
} else {
toolsElement.style.top = `${window.innerHeight - 100}px`;
}
}
}
});
onUnmounted(() => {
const toolsElement = toolsRef.value;
if (toolsElement) {
toolsElement.removeEventListener('mousedown', startDragging);
}
document.removeEventListener('mousemove', handleDragging);
document.removeEventListener('mouseup', stopDragging);
});
};