feat(ripple): 水波纹组件
This commit is contained in:
parent
90f89c7c5f
commit
8ece0bd5f7
73
package/component/src/component/ripple/index.less
Normal file
73
package/component/src/component/ripple/index.less
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
.layui-water-ripples-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
.layui-slot-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-out-ripples {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-animate-once--out {
|
||||||
|
animation: ripple-effect 1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-animate-always--out {
|
||||||
|
animation: ripple-effect 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple-effect {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0px var(--layui-ripple-color);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 var(--layui-spread-width) var(--layui-ripple-color);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-inset-ripples {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
opacity: 1;
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
background: var(--layui-ripple-color);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes size-effect {
|
||||||
|
0% {
|
||||||
|
width: 0px;
|
||||||
|
height: 0px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
width: var(--layui-spread-size);
|
||||||
|
height: var(--layui-spread-size);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-animate-once--inset {
|
||||||
|
animation: size-effect 1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layui-animate-always--inset {
|
||||||
|
animation: size-effect 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
8
package/component/src/component/ripple/index.ts
Normal file
8
package/component/src/component/ripple/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import type { App } from "vue";
|
||||||
|
import Component from "./index.vue";
|
||||||
|
|
||||||
|
Component.install = (app: App) => {
|
||||||
|
app.component(Component.name, Component);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Component;
|
115
package/component/src/component/ripple/index.vue
Normal file
115
package/component/src/component/ripple/index.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: "LayRipple",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
|
import "./index.less";
|
||||||
|
|
||||||
|
export interface LayRippletProps {
|
||||||
|
type?: 'out' | 'inset';
|
||||||
|
color?: string;
|
||||||
|
borderRadius?: string;
|
||||||
|
spreadWidth?: string;
|
||||||
|
spreadSize?: string;
|
||||||
|
trigger?: 'always' | 'mouseenter' | 'click';
|
||||||
|
center?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<LayRippletProps>(), {
|
||||||
|
type: "inset",
|
||||||
|
color: "currentColor",
|
||||||
|
borderRadius: "0",
|
||||||
|
spreadWidth: "6px",
|
||||||
|
trigger: "click",
|
||||||
|
center: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isActiveRef = ref(false);
|
||||||
|
// 波纹宽高
|
||||||
|
const spreadSizeRef = ref<string>("0px")
|
||||||
|
const ripplesRefEl = ref<HTMLElement | null>(null);
|
||||||
|
const waterRipplesContainerRefEl = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const isOut = computed(() => {
|
||||||
|
return props.type === "out"
|
||||||
|
})
|
||||||
|
|
||||||
|
const rippleX = ref<string | number | undefined>(undefined);
|
||||||
|
const rippleY = ref<string | number | undefined>(undefined);
|
||||||
|
|
||||||
|
const onActive = function (event: Event) {
|
||||||
|
isActiveRef.value = true;
|
||||||
|
|
||||||
|
// 跟随鼠标点击
|
||||||
|
if (props.type === "inset" && !props.spreadSize && !props.center) {
|
||||||
|
const el = event.currentTarget
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const rippleOffsetLeft = event.clientX - rect.left;
|
||||||
|
const rippleOffsetTop = event.clientY - rect.top;
|
||||||
|
const sizeX = Math.max(rippleOffsetLeft, rect.width - rippleOffsetLeft);
|
||||||
|
const sizeY = Math.max(rippleOffsetTop, rect.height - rippleOffsetTop);
|
||||||
|
rippleX.value = rippleOffsetLeft + 'px';
|
||||||
|
rippleY.value = rippleOffsetTop + 'px';
|
||||||
|
spreadSizeRef.value = Math.sqrt(sizeX ** 2 + sizeY ** 2) * 2 + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initWidth = function () {
|
||||||
|
let container = waterRipplesContainerRefEl.value;
|
||||||
|
let ripples = ripplesRefEl.value;
|
||||||
|
if (!container || !ripples) return;
|
||||||
|
if (props.type == "out") {
|
||||||
|
ripples.style.width = container.clientWidth + "px";
|
||||||
|
} else {
|
||||||
|
container.style.overflow = "hidden";
|
||||||
|
if (!props.spreadSize || props.center) {
|
||||||
|
spreadSizeRef.value = container.clientWidth * 1.1 + "px"
|
||||||
|
} else {
|
||||||
|
spreadSizeRef.value = props.spreadSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ripples.addEventListener('animationend', () => {
|
||||||
|
isActiveRef.value = false
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initWidth();
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.trigger,
|
||||||
|
(val) => isActiveRef.value = (val === 'always'),
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layui-water-ripples-container" ref="waterRipplesContainerRefEl" @[trigger]="onActive">
|
||||||
|
<div
|
||||||
|
ref="ripplesRefEl"
|
||||||
|
:class="{
|
||||||
|
'layui-out-ripples': isOut,
|
||||||
|
'layui-inset-ripples': type == 'inset',
|
||||||
|
'layui-animate-always--out': isActiveRef && trigger == 'always' && type == 'out',
|
||||||
|
'layui-animate-once--out': isActiveRef && (trigger == 'mouseenter' || trigger == 'click') && type == 'out',
|
||||||
|
'layui-animate-always--inset': isActiveRef && trigger == 'always' && type == 'inset',
|
||||||
|
'layui-animate-once--inset': isActiveRef && (trigger == 'mouseenter' || trigger == 'click') && type == 'inset',
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
borderRadius: isOut ? borderRadius : '50%',
|
||||||
|
left: rippleX,
|
||||||
|
top: rippleY,
|
||||||
|
'--layui-ripple-color': color,
|
||||||
|
'--layui-spread-width': spreadWidth,
|
||||||
|
'--layui-spread-size': spreadSizeRef,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -82,6 +82,7 @@ import LayDatePicker from "./component/datePicker/index";
|
|||||||
import LayNoticeBar from "./component/noticeBar/index";
|
import LayNoticeBar from "./component/noticeBar/index";
|
||||||
import LayTransition from "./component/transition/index";
|
import LayTransition from "./component/transition/index";
|
||||||
import LayUpload from "./component/upload/index";
|
import LayUpload from "./component/upload/index";
|
||||||
|
import LayRipple from "./component/ripple/index";
|
||||||
import LayConfigProvider from "./provider";
|
import LayConfigProvider from "./provider";
|
||||||
import { InstallOptions } from "./types";
|
import { InstallOptions } from "./types";
|
||||||
|
|
||||||
@ -162,6 +163,7 @@ const components: Record<string, Component> = {
|
|||||||
LayNoticeBar,
|
LayNoticeBar,
|
||||||
LayTransition,
|
LayTransition,
|
||||||
LayUpload,
|
LayUpload,
|
||||||
|
LayRipple,
|
||||||
};
|
};
|
||||||
|
|
||||||
const install = (app: App, options?: InstallOptions): void => {
|
const install = (app: App, options?: InstallOptions): void => {
|
||||||
@ -250,6 +252,7 @@ export {
|
|||||||
LayNoticeBar,
|
LayNoticeBar,
|
||||||
LayTransition,
|
LayTransition,
|
||||||
LayUpload,
|
LayUpload,
|
||||||
|
LayRipple,
|
||||||
install,
|
install,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
48
package/document/src/document/zh-CN/components/ripple.md
Normal file
48
package/document/src/document/zh-CN/components/ripple.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
::: anchor
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: title 基本介绍
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: describe 为组件添加水波纹动画。
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: title 测试
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: demo 使用 `lay-ripple` 标签, 添加水波纹
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<lay-ripple>
|
||||||
|
<lay-button>内部:click</lay-button>
|
||||||
|
</lay-ripple><br>
|
||||||
|
<lay-ripple trigger="always">
|
||||||
|
<lay-button>内部:always</lay-button>
|
||||||
|
</lay-ripple><br>
|
||||||
|
<lay-ripple trigger="mouseenter">
|
||||||
|
<lay-button>内部:mouseenter</lay-button>
|
||||||
|
</lay-ripple><br><br>
|
||||||
|
<lay-ripple type="out" borderRadius="1px">
|
||||||
|
<lay-button>外部:click</lay-button>
|
||||||
|
</lay-ripple><br><br>
|
||||||
|
<lay-ripple type="out" trigger="always" borderRadius="50%" color="red">
|
||||||
|
<div style="border-radius:50%;background-color:red;width:8px;height:8px"></div>
|
||||||
|
</lay-ripple><br><br><br>
|
||||||
|
<lay-ripple type="out" trigger="mouseenter" borderRadius="50%" color="red">
|
||||||
|
<div style="border-radius:50%;background-color:red;width:20px;height:20px"></div>
|
||||||
|
</lay-ripple><br>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
:::
|
@ -381,7 +381,13 @@ const zhCN = [
|
|||||||
path: "/zh-CN/components/transition",
|
path: "/zh-CN/components/transition",
|
||||||
component: () =>
|
component: () =>
|
||||||
import("../document/zh-CN/components/transition.md"),
|
import("../document/zh-CN/components/transition.md"),
|
||||||
meta: { title: "通知栏" },
|
meta: { title: "过渡" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/zh-CN/components/ripple",
|
||||||
|
component: () =>
|
||||||
|
import("../document/zh-CN/components/ripple.md"),
|
||||||
|
meta: { title: "水波纹" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -34,11 +34,17 @@ const menus = [
|
|||||||
path: "/zh-CN/components/transition",
|
path: "/zh-CN/components/transition",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 101,
|
id: 102,
|
||||||
title: "全屏",
|
title: "全屏",
|
||||||
subTitle: "fullscreen",
|
subTitle: "fullscreen",
|
||||||
path: "/zh-CN/components/fullscreen",
|
path: "/zh-CN/components/fullscreen",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 103,
|
||||||
|
title: "水波纹",
|
||||||
|
subTitle: "ripple",
|
||||||
|
path: "/zh-CN/components/ripple",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user