Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
feat: add ripple demo (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
DesignHhuang authored Nov 7, 2023
1 parent 186d279 commit 89c7e86
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 5 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,8 @@
"esbuild"
],
"commentTranslate.multiLineMerge": true,
"typescript.suggest.autoImports": false
"typescript.suggest.autoImports": false,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
23 changes: 23 additions & 0 deletions apps/admin/src/pages/demo/feat/ripple.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { rippleDirective as vRipple } from '@vben/directives'
</script>

<template>
<VbenCard title="Ripple示例">
<div class="demo-box" v-ripple>content</div>
</VbenCard>
</template>

<style lang="less" scoped>
.demo-box {
display: flex;
align-items: center;
justify-content: center;
width: 300px;
height: 300px;
border-radius: 10px;
background-color: #408ede;
color: #fff;
font-size: 24px;
}
</style>
1 change: 1 addition & 0 deletions packages/directives/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { clickOutside } from './src/click-outside'
export { rippleDirective } from './src/ripple/index'
3 changes: 3 additions & 0 deletions packages/directives/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@
"dependencies": {
"@vben/utils": "workspace:*",
"vue": "3.3.4"
},
"devDependencies": {
"@vben/types": "workspace:*"
}
}
21 changes: 21 additions & 0 deletions packages/directives/src/ripple/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.ripple-container {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
overflow: hidden;
pointer-events: none;
}

.ripple-effect {
position: relative;
z-index: 9999;
width: 1px;
height: 1px;
margin-top: 0;
margin-left: 0;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 50%;
pointer-events: none;
}
197 changes: 197 additions & 0 deletions packages/directives/src/ripple/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import type { Directive } from 'vue'
import './index.less'

export interface RippleOptions {
event: string
transition: number
}

export interface RippleProto {
background?: string
zIndex?: string
}

export type EventType = Event & MouseEvent & TouchEvent

const options: RippleOptions = {
event: 'mousedown',
transition: 400,
}

const rippleDirective: Directive & RippleProto = {
beforeMount: (el: HTMLElement, binding) => {
if (binding.value === false) return

const bg = el.getAttribute('ripple-background')
setProps(Object.keys(binding.modifiers), options)

const background = bg || rippleDirective.background
const zIndex = rippleDirective.zIndex

el.addEventListener(options.event, (event: Event) => {
rippler({
event: event as EventType,
el,
background,
zIndex,
})
})
},
updated(el, binding) {
if (!binding.value) {
el?.clearRipple?.()
return
}
const bg = el.getAttribute('ripple-background')
el?.setBackground?.(bg)
},
}

function rippler({
event,
el,
zIndex,
background,
}: { event: EventType; el: HTMLElement } & RippleProto) {
const targetBorder = parseInt(
getComputedStyle(el).borderWidth.replace('px', ''),
)
const clientX = event.clientX || event.touches[0].clientX
const clientY = event.clientY || event.touches[0].clientY

const rect = el.getBoundingClientRect()
const { left, top } = rect
const { offsetWidth: width, offsetHeight: height } = el
const { transition } = options
const dx = clientX - left
const dy = clientY - top
const maxX = Math.max(dx, width - dx)
const maxY = Math.max(dy, height - dy)
const style = window.getComputedStyle(el)
const radius = Math.sqrt(maxX * maxX + maxY * maxY)
const border = targetBorder > 0 ? targetBorder : 0

const ripple = document.createElement('div')
const rippleContainer = document.createElement('div')

// Styles for ripple
ripple.className = 'ripple'

Object.assign(ripple.style ?? {}, {
marginTop: '0px',
marginLeft: '0px',
width: '1px',
height: '1px',
transition: `all ${transition}ms cubic-bezier(0.4, 0, 0.2, 1)`,
borderRadius: '50%',
pointerEvents: 'none',
position: 'relative',
zIndex: zIndex ?? '9999',
backgroundColor: background ?? 'rgba(0, 0, 0, 0.12)',
})

// Styles for rippleContainer
rippleContainer.className = 'ripple-container'
Object.assign(rippleContainer.style ?? {}, {
position: 'absolute',
left: `${0 - border}px`,
top: `${0 - border}px`,
height: '0',
width: '0',
pointerEvents: 'none',
overflow: 'hidden',
})

const storedTargetPosition =
el.style.position.length > 0
? el.style.position
: getComputedStyle(el).position

if (storedTargetPosition !== 'relative') {
el.style.position = 'relative'
}

rippleContainer.appendChild(ripple)
el.appendChild(rippleContainer)

Object.assign(ripple.style, {
marginTop: `${dy}px`,
marginLeft: `${dx}px`,
})

const {
borderTopLeftRadius,
borderTopRightRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
} = style
Object.assign(rippleContainer.style, {
width: `${width}px`,
height: `${height}px`,
direction: 'ltr',
borderTopLeftRadius,
borderTopRightRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
})

setTimeout(() => {
const wh = `${radius * 2}px`
Object.assign(ripple.style ?? {}, {
width: wh,
height: wh,
marginLeft: `${dx - radius}px`,
marginTop: `${dy - radius}px`,
})
}, 0)

function clearRipple() {
setTimeout(() => {
ripple.style.backgroundColor = 'rgba(0, 0, 0, 0)'
}, 250)

setTimeout(() => {
rippleContainer?.parentNode?.removeChild(rippleContainer)
}, 850)
el.removeEventListener('mouseup', clearRipple, false)
el.removeEventListener('mouseleave', clearRipple, false)
el.removeEventListener('dragstart', clearRipple, false)
setTimeout(() => {
let clearPosition = true
for (let i = 0; i < el.childNodes.length; i++) {
if ((el.childNodes[i] as Recordable).className === 'ripple-container') {
clearPosition = false
}
}

if (clearPosition) {
el.style.position =
storedTargetPosition !== 'static' ? storedTargetPosition : ''
}
}, options.transition + 260)
}

if (event.type === 'mousedown') {
el.addEventListener('mouseup', clearRipple, false)
el.addEventListener('mouseleave', clearRipple, false)
el.addEventListener('dragstart', clearRipple, false)
} else {
clearRipple()
}

;(el as Recordable).setBackground = (bgColor: string) => {
if (!bgColor) {
return
}
ripple.style.backgroundColor = bgColor
}
}

function setProps(modifiers: Recordable, props: Recordable) {
modifiers.forEach((item: Recordable) => {
if (isNaN(Number(item))) props.event = item
else props.transition = item
})
}

export { rippleDirective }
3 changes: 3 additions & 0 deletions packages/locale/src/lang/en/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@ export default {
role: 'Role management',
},
strength: 'Password strength',
feat: {
ripple: 'Ripple',
},
},
}
3 changes: 3 additions & 0 deletions packages/locale/src/lang/zh-CN/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,8 @@ export default {
role: '角色管理',
},
strength: '密码强度组件',
feat: {
ripple: '水波纹',
},
},
}
16 changes: 12 additions & 4 deletions packages/router/src/routes/modules/demo/feat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ const feat: RouteRecordItem = {
path: '/feat',
name: 'Feat',
component: LAYOUT,
redirect: '/feat/index',
redirect: '/feat/ripple',
meta: {
orderNo: 4,
title: '功能',
icon: 'ri:function-line',
root: true
root: true,
},
children: []
children: [
{
path: 'ripple',
name: 'RippleDemo',
component: () => import('@/pages/demo/feat/ripple.vue'),
meta: {
title: 'routes.demo.feat.ripple',
},
},
],
}

export default feat

13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 89c7e86

Please sign in to comment.