133 lines
3.5 KiB
Plaintext
133 lines
3.5 KiB
Plaintext
<script lang="ts">
|
|
export default {
|
|
name: "LayRipple",
|
|
};
|
|
</script>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
import "./index.less";
|
|
import { RippleTrigger, RippleType } from "./interface";
|
|
|
|
export interface RippleProps {
|
|
type?: RippleType;
|
|
color?: string;
|
|
borderRadius?: string;
|
|
spreadWidth?: string;
|
|
spreadSize?: string;
|
|
trigger?: RippleTrigger;
|
|
center?: boolean;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<RippleProps>(), {
|
|
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;
|
|
// @ts-ignore
|
|
const rect = el.getBoundingClientRect();
|
|
// @ts-ignore
|
|
const rippleOffsetLeft = event.clientX - rect.left;
|
|
// @ts-ignore
|
|
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>
|