z自定义指令
This commit is contained in:
32
src/App.vue
32
src/App.vue
@@ -1,25 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Header />
|
<MainHeader />
|
||||||
<RouterView />
|
<RouterView />
|
||||||
<Footer />
|
<MainFooter />
|
||||||
<BackTop />
|
<BackTop />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from "vue-router";
|
||||||
import Header from './components/header.vue'
|
import MainHeader from "./components/main-header.vue";
|
||||||
import Footer from './components/footer.vue'
|
import MainFooter from "./components/main-footer.vue";
|
||||||
import BackTop from "./components/back-top.vue";
|
import BackTop from "./components/back-top.vue";
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
// .main {
|
// .main {
|
||||||
// height: 100vh;
|
// height: 100vh;
|
||||||
// width: 100vw;
|
// width: 100vw;
|
||||||
// overflow-y: auto;
|
// overflow-y: auto;
|
||||||
// overflow-x: hidden;
|
// overflow-x: hidden;
|
||||||
// position: relative;
|
// position: relative;
|
||||||
// > .content{
|
// > .content{
|
||||||
// height: auto;
|
// height: auto;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="back-top" @click="handleClick" v-scroll-progress="handleScroll">
|
||||||
class="back-top"
|
|
||||||
@click="handleClick"
|
|
||||||
:class="[progress > 0 ? 'active' : 'hidden']"
|
|
||||||
>
|
|
||||||
<svg width="100%" height="100%" viewBox="-1 -1 102 102">
|
<svg width="100%" height="100%" viewBox="-1 -1 102 102">
|
||||||
<path
|
<path
|
||||||
d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98"
|
d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98"
|
||||||
@@ -20,26 +16,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, h } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
const max = ref(98 * Math.PI);
|
const max = ref(98 * Math.PI);
|
||||||
const progress = ref(0);
|
const progress = ref(0);
|
||||||
const handleScroll = () => {
|
const handleScroll = (e: number) => {
|
||||||
const el = document.documentElement;
|
progress.value = (e / 100) * max.value;
|
||||||
const num = el.scrollTop / (el.scrollHeight - el.clientHeight);
|
|
||||||
progress.value = max.value * num;
|
|
||||||
};
|
};
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
|
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
|
||||||
handleScroll();
|
|
||||||
document.addEventListener("scroll", handleScroll);
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener("scroll", handleScroll);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped lang="less">
|
||||||
.back-top {
|
.back-top {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
@@ -53,40 +40,43 @@
|
|||||||
-webkit-box-shadow: inset 0 0 0 1px #e1e1e1;
|
-webkit-box-shadow: inset 0 0 0 1px #e1e1e1;
|
||||||
box-shadow: inset 0 0 0 1px #e1e1e1;
|
box-shadow: inset 0 0 0 1px #e1e1e1;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
display: flex;
|
||||||
.back-top svg {
|
align-items: center;
|
||||||
width: 100%;
|
justify-content: center;
|
||||||
height: 100%;
|
> svg {
|
||||||
}
|
position: absolute;
|
||||||
.back-top path {
|
width: 100%;
|
||||||
transition: stroke-dashoffset 10ms linear;
|
height: 100%;
|
||||||
}
|
> path {
|
||||||
.back-top.active {
|
transition: stroke-dashoffset 10ms linear;
|
||||||
animation: active 0.2s linear both;
|
}
|
||||||
}
|
|
||||||
.back-top.hidden {
|
|
||||||
animation: hidden 0.2s linear both;
|
|
||||||
}
|
|
||||||
@keyframes active {
|
|
||||||
0% {
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(50px);
|
|
||||||
}
|
}
|
||||||
100% {
|
|
||||||
opacity: 1;
|
animation: back-top-hidden 0.2s linear both;
|
||||||
transform: translateY(0);
|
&.active {
|
||||||
|
animation: back-top-active 0.2s linear both;
|
||||||
}
|
}
|
||||||
}
|
@keyframes back-top-active {
|
||||||
@keyframes hidden {
|
0% {
|
||||||
0% {
|
display: none;
|
||||||
opacity: 1;
|
opacity: 0;
|
||||||
transform: translateY(0);
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
100% {
|
@keyframes back-top-hidden {
|
||||||
opacity: 0;
|
0% {
|
||||||
transform: translateY(50px);
|
opacity: 1;
|
||||||
display: none;
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(15px);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
|
||||||
//const props = defineProps({
|
|
||||||
//})
|
|
||||||
//const emit = defineEmits([
|
|
||||||
//])
|
|
||||||
let data = reactive({
|
|
||||||
})
|
|
||||||
onMounted(()=>{
|
|
||||||
})
|
|
||||||
onUnmounted(()=>{
|
|
||||||
})
|
|
||||||
defineExpose({})
|
|
||||||
const {} = toRefs(data);
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<header class="header">
|
|
||||||
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.header{
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
20
src/components/main-header.vue
Normal file
20
src/components/main-header.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<header class="main-header" v-scroll-progress></header>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.main-header {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 85px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
transition: background-color 0.2s linear;
|
||||||
|
&.active {
|
||||||
|
background-color: #0a0a0a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
10
src/directives/index.ts
Normal file
10
src/directives/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { App } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(app: App) {
|
||||||
|
const directivesList = import.meta.glob('./*.ts', { eager: true }) as any;
|
||||||
|
Object.keys(directivesList).forEach(key => {
|
||||||
|
app.directive(directivesList[key].default.name, directivesList[key].default);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
63
src/directives/scroll-progress.ts
Normal file
63
src/directives/scroll-progress.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 监听滚动条 滚动进度变化
|
||||||
|
* v-scroll-progress.self="(p) => console.log(p)"
|
||||||
|
* @modifiers self 监听当前元素的滚动进度变化
|
||||||
|
* @param params 滚动进度回调函数
|
||||||
|
* @param p 滚动进度
|
||||||
|
*/
|
||||||
|
const rootMap = new Map()
|
||||||
|
export default {
|
||||||
|
name: 'scroll-progress',
|
||||||
|
mounted(el: HTMLElement, binding: any) {
|
||||||
|
const params = binding.value
|
||||||
|
const { self } = binding.modifiers
|
||||||
|
const paramsType = typeof params
|
||||||
|
const obj = {
|
||||||
|
onscroll: () => { },
|
||||||
|
el,
|
||||||
|
root: self ? el : document,
|
||||||
|
activeNum: 0,
|
||||||
|
};
|
||||||
|
if (paramsType === 'function') {
|
||||||
|
obj.onscroll = params
|
||||||
|
} else if (paramsType === 'object') {
|
||||||
|
if (params.onscroll) obj.onscroll = params.onscroll
|
||||||
|
if (params.GetRoot) obj.root = params.GetRoot()
|
||||||
|
if (params.hasOwnProperty('activeNum')) obj.activeNum = params.activeNum
|
||||||
|
}
|
||||||
|
requestAnimationFrame(() => handleScroll({ target: obj.root }))
|
||||||
|
if (rootMap.has(obj.root)) {
|
||||||
|
rootMap.get(obj.root).push(obj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rootMap.set(obj.root, [obj])
|
||||||
|
obj.root.addEventListener('scroll', handleScroll)
|
||||||
|
},
|
||||||
|
beforeUnmount(el: HTMLElement, binding: any) {
|
||||||
|
rootMap.forEach((objs, root) => {
|
||||||
|
if (objs.some((v: any) => v.el === el)) {
|
||||||
|
objs = objs.filter((v_: any) => v_.el !== el)
|
||||||
|
rootMap.set(root, objs)
|
||||||
|
}
|
||||||
|
if (objs.length === 0) {
|
||||||
|
root.removeEventListener('scroll', handleScroll)
|
||||||
|
rootMap.delete(root)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function handleScroll(e: any) {
|
||||||
|
const target = e.target
|
||||||
|
const objs = rootMap.get(target)
|
||||||
|
if (!objs || objs.length === 0) return
|
||||||
|
const el = target === document ? document.documentElement : target as HTMLElement
|
||||||
|
const num = el.scrollTop / (el.scrollHeight - el.clientHeight);
|
||||||
|
const progress = Math.round(num * 100)
|
||||||
|
objs.forEach((obj: any) => {
|
||||||
|
obj?.onscroll(progress)
|
||||||
|
if (obj.el) {
|
||||||
|
let isActive = progress > obj.activeNum
|
||||||
|
obj.el.classList.toggle('active', isActive)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
@@ -2,8 +2,13 @@ import { ViteSSG } from 'vite-ssg'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { routes } from './routes'
|
import { routes } from './routes'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
import directives from './directives/index'
|
||||||
|
|
||||||
|
|
||||||
|
// 注册指令
|
||||||
export const createApp = ViteSSG(App, {
|
export const createApp = ViteSSG(App, {
|
||||||
routes,
|
routes,
|
||||||
base: import.meta.env.BASE_URL,
|
base: import.meta.env.BASE_URL,
|
||||||
|
}, ({ app }) => {
|
||||||
|
app.use(directives)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user