css 实现时钟小组件
圆盘时钟
<script lang='ts' setup>
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
import { useTimeoutPoll } from '@vueuse/core'
import { gsap } from 'gsap'
const hour = ref(0)
const minute = ref(0)
const second = ref(0)
function calculTime() {
const date = new Date()
hour.value = date.getHours()
minute.value = date.getMinutes()
second.value = date.getSeconds()
}
function mapNum(num: number): string {
return num < 10 ? `0${num}` : `${num}`
}
const timeStampArr = computed(() => {
return `${mapNum(hour.value)}:${mapNum(minute.value)}:${mapNum(second.value)}`.split('')
})
const { isActive, pause, resume } = useTimeoutPoll(calculTime, 1000)
if (!isActive.value)
resume()
nextTick(() => {
gsap.set('.char', {
'--hue': gsap.utils.distribute({
base: 0,
amount: 330,
}),
})
gsap.to('.char', {
'--weight': 700,
'--saturation': 80,
'ease': 'sine.inOut',
'duration': 0.5,
'stagger': {
yoyo: true,
repeat: -1,
each: 0.15,
},
}).time(2)
gsap.set('.clock-number-item', {
'--hue': gsap.utils.distribute({
base: 0,
amount: 330,
}),
})
gsap.to('.clock-number-item', {
'--weight': 700,
'--saturation': 80,
'ease': 'sine.inOut',
'duration': 0.5,
'stagger': {
yoyo: true,
repeat: -1,
each: 0.15,
},
}).time(2)
})
onBeforeUnmount(() => {
if (isActive.value)
pause()
})
</script>
<template>
<div class="custom-wrapper p-10">
<div class="container px-12 py-6 rounded-10px">
<div class="w-36 h-36 flex items-center justify-center rounded-50% relative clock-wrap">
<div class="clock-pointer absolute w-2 h-2 rounded-50% z-30" />
<div class="clock-marker w-92% h-92% rounded-50% relative">
<div class="clock-marker-item absolute rounded-0.4 w-2px h-2 left-50% top-4px translate-x--50%" />
<div class="clock-marker-item absolute rounded-0.4 w-2px h-2 left-50% bottom-4px translate-x--50%" />
<div class="clock-marker-item absolute rounded-0.4 h-2px w-2 top-50% left-4px translate-y--50%" />
<div class="clock-marker-item absolute rounded-0.4 h-2px w-2 top-50% right-4px translate-y--50%" />
<div class="clock-marker-inner w-60% h-60% absolute rounded-50% blur-1 left-20% top-20%" />
</div>
<!-- 时 -->
<div class="clock-hour absolute origin-bottom rounded-.8 h-27.6% w-1.6 top-22.4% z-10" :style="{ rotate: `${hour * 30 + minute / 60 * 30}deg` }" />
<!-- 分 -->
<div class="clock-minute absolute origin-bottom rounded-.8 h-35% w-1.2 top-15% z-15" :style="{ rotate: `${minute * 6}deg` }" />
<!-- 秒 -->
<div class="clock-second absolute origin-bottom rounded-.8 h-43% w-.8 top-7% z-20" :style="{ rotate: `${second * 6}deg` }" />
</div>
<div class="clock-text text-6xl pt-6">
<span class="char">
H
</span>
<span class="char">
e
</span>
<span class="char">
l
</span>
<span class="char">
l
</span>
<span class="char">
o
</span>
<span class="char" />
<span class="char">
W
</span>
<span class="char">
o
</span>
<span class="char">
r
</span>
<span class="char">
l
</span>
<span class="char">
d
</span>
</div>
<div class="clock-text text-6xl pt-6">
<span
v-for="(txt) in timeStampArr"
:key="txt"
class="clock-number-item inline-block w-auto text-center"
>
{{ txt }}
</span>
</div>
</div>
</div>
</template>
<style lang='scss' scoped>
.custom-wrapper {
--primary-light: #8abdff;
--primary: #6d5dfc;
--primary-dark: #5b0eeb;
--white: #FFFFFF;
--greyLight-1: #E4EBF5;
--greyLight-2: #c8d0e7;
--greyLight-3: #bec8e4;
--greyDark: #9baacf;
$shadow: .3rem .3rem .6rem var(--greyLight-2), -.2rem -.2rem .5rem var(--white);
$inner-shadow: inset .2rem .2rem .5rem var(--greyLight-2), inset -.2rem -.2rem .5rem var(--white);
background-color: var(--greyLight-1);
.container {
box-shadow:.8rem .8rem 1.4rem var(--greyLight-2), -.2rem -.2rem 1.8rem var(--white);
.clock-wrap {
box-shadow: 0.3rem 0.3rem 0.6rem var(--greyLight-2), -0.2rem -0.2rem 0.5rem var(--white);
.clock-pointer {
background: var(--primary);
}
.clock-marker {
box-shadow: inset 0.2rem 0.2rem 0.5rem var(--greyLight-2), inset -0.2rem -0.2rem 0.5rem var(--white);
div {
box-shadow: inset 1px 1px 1px var(--greyLight-2), inset -1px -1px 1px var(--white);
}
}
.clock-hour {
background: var(--greyDark);
}
.clock-minute {
background-color: var(--greyLight-3);
}
.clock-second {
background-color: var(--primary);
}
}
.clock-text {
span {
font-variation-settings: 'wght' var(--weight, 100);
color: hsl(var(--hue), calc(var(--saturation) * 1%), 65%);
}
}
}
}
</style>
电子数字表盘
<script lang='ts' setup>
import { computed, onBeforeUnmount, ref } from 'vue'
import { useTimeoutPoll } from '@vueuse/core'
const hour = ref(0)
const minute = ref(0)
const second = ref(0)
function calculTime() {
const date = new Date()
hour.value = date.getHours()
minute.value = date.getMinutes()
second.value = date.getSeconds()
}
function mapNum(num: number): string {
return num < 10 ? `0${num}` : `${num}`
}
function mapNumCls(idx, num) {
let str = ''
switch (idx) {
case 1:
str = `w-15 border-t-black left-0 top--8px ${[1, 4].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
case 2:
str = `w-15 border-t-black left-0 top-15 ${[0, 1, 7].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
case 3:
str = `w-15 border-b-black left-0 bottom--8px ${[1, 4, 7].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
case 4:
str = `h-15 border-l-black left--4px top--4px ${[1, 2, 3].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
case 5:
str = `h-15 border-r-black right--4px top--4px ${[5, 6].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
case 6:
str = `h-15 border-l-black left--4px bottom--4px ${[1, 3, 4, 5, 7, 9].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
case 7:
str = `h-15 border-r-black right--4px bottom--4px ${[2].includes(num) ? 'opacity-0' : 'opacity-100'}`
break
default:
break
}
return str
}
const timeStampArr = computed(() => {
return `${mapNum(hour.value)}:${mapNum(minute.value)}:${mapNum(second.value)}`.split('')
})
const { isActive, pause, resume } = useTimeoutPoll(calculTime, 1000)
if (!isActive.value)
resume()
console.log(timeStampArr.value)
onBeforeUnmount(() => {
if (isActive.value)
pause()
})
</script>
<template>
<div class="custom-wrapper">
<div class="number-content p-4">
<div v-for="num in timeStampArr" :key="num" class="inline-block">
<div v-if="Number(num) >= 0" class="number-box h-30 w-15 relative mr-4">
<div
v-for="i in 7"
:key="`line-${i}`"
class="line absolute border-solid border-transparent border-8 transition-opacity linear duration-500"
:class="`line-${i} current-${num} ${mapNumCls(i, Number(num))}`"
/>
</div>
<div v-else class="number-box h-30 w-15 relative">
<span class="point w-2 h-2 rounded-50% absolute left-6.5 bg-black top-9 animate-iteration-infinite animate-fade-in animate-linear" />
<span class="point w-2 h-2 rounded-50% absolute left-6.5 bg-black top-19 animate-iteration-infinite animate-fade-in animate-linear" />
</div>
</div>
</div>
</div>
</template>
<style lang='scss' scoped>
</style>
css 3D时钟
TIP
如果你的元素设置了transform-style值为preserve-3d,就不能为了防止子元素溢出容器而设置overflow值为hidden,如果设置了overflow:hidden同样可以迫死子元素出现在同一平面(和元素设置了transform-style为flat一样的效果)!!!!
🗣🗣🗣 👉👉👉: >点击下方按钮切换查看效果<👇👇👇👇👇👇
<script lang='ts' setup>
import { computed, onBeforeUnmount, ref } from 'vue'
import { useTimeoutPoll } from '@vueuse/core'
import { ElRadioButton, ElRadioGroup } from 'element-plus'
const hour = ref(0)
const minute = ref(0)
const second = ref(0)
const toggleHidden = ref('unset')
function calculTime() {
const date = new Date()
hour.value = date.getHours()
minute.value = date.getMinutes()
second.value = date.getSeconds()
}
function mapNum(num: number): string {
return num < 10 ? `0${num}` : `${num}`
}
const timeStampArr = computed(() => {
return `${mapNum(hour.value)}${mapNum(minute.value)}${mapNum(second.value)}`.split('').map(el => Number(el))
})
const { isActive, pause, resume } = useTimeoutPoll(calculTime, 1000)
if (!isActive.value)
resume()
onBeforeUnmount(() => {
if (isActive.value)
pause()
})
</script>
<template>
<div class="custom-wrapper bg-#222222 py-16 relative overflow-hidden">
<ElRadioGroup v-model="toggleHidden" class="absolute z-10 left-4 top-4" size="small">
<ElRadioButton label="unset" />
<ElRadioButton label="hidden" />
</ElRadioGroup>
<div class="clock w-full h-6em flex items-center justify-center text-4vmin font-mono preserve-3d perspect-500 text-#F1F1F1 tracking-0.1em font-bold" :style="{ overflow: toggleHidden }">
<!-- 时 -->
<span class="clock-item relative w-1ch h-1ch inline-block preserve-3d mx-0.2ch transition-transform duration-300 ease" :style="{ '--index': `${timeStampArr[0]}` }">
<span v-for="i in 10" :key="`items-0-${i}`" class="absolute left-0 top-0 w-1ch h-1ch inline-block" :style="{ transform: `rotateX(${(i - 1) * 36}deg) translateZ(2.5em)` }">{{ i - 1 }}</span>
</span>
<span class="clock-item relative w-1ch h-1ch inline-block preserve-3d mx-0.2ch transition-transform duration-300 ease" :style="{ '--index': `${timeStampArr[1]}` }">
<span v-for="i in 10" :key="`items-0-${i}`" class="absolute left-0 top-0 w-1ch h-1ch inline-block" :style="{ transform: `rotateX(${(i - 1) * 36}deg) translateZ(2.5em)` }">{{ i - 1 }}</span>
</span>
<!-- 冒号 -->
<div class="relative w-1ch h-1ch mx-0.2ch inline-block opacity-80 text-center translate-z-2.5em">
:
</div>
<!-- 分 -->
<span class="clock-item relative w-1ch h-1ch inline-block preserve-3d mx-0.2ch transition-transform duration-300 ease" :style="{ '--index': `${timeStampArr[2]}` }">
<span v-for="i in 10" :key="`items-0-${i}`" class="absolute left-0 top-0 w-1ch h-1ch inline-block" :style="{ transform: `rotateX(${(i - 1) * 36}deg) translateZ(2.5em)` }">{{ i - 1 }}</span>
</span>
<span class="clock-item relative w-1ch h-1ch inline-block preserve-3d mx-0.2ch transition-transform duration-300 ease" :style="{ '--index': `${timeStampArr[3]}` }">
<span v-for="i in 10" :key="`items-0-${i}`" class="absolute left-0 top-0 w-1ch h-1ch inline-block" :style="{ transform: `rotateX(${(i - 1) * 36}deg) translateZ(2.5em)` }">{{ i - 1 }}</span>
</span>
<!-- 冒号 -->
<div class="relative w-1ch h-1ch mx-0.2ch inline-block opacity-80 text-center translate-z-2.5em">
:
</div>
<!-- 秒 -->
<span class="clock-item relative w-1ch h-1ch inline-block preserve-3d mx-0.2ch transition-transform duration-300 ease" :style="{ '--index': `${timeStampArr[4]}` }">
<span v-for="i in 10" :key="`items-0-${i}`" class="absolute left-0 top-0 w-1ch h-1ch inline-block" :style="{ transform: `rotateX(${(i - 1) * 36}deg) translateZ(2.5em)` }">{{ i - 1 }}</span>
</span>
<span class="clock-item relative w-1ch h-1ch inline-block preserve-3d mx-0.2ch transition-transform duration-300 ease" :style="{ '--index': `${timeStampArr[5]}` }">
<span v-for="i in 10" :key="`items-0-${i}`" class="absolute left-0 top-0 w-1ch h-1ch inline-block" :style="{ transform: `rotateX(${(i - 1) * 36}deg) translateZ(2.5em)` }">{{ i - 1 }}</span>
</span>
</div>
</div>
</template>
<style lang='scss' scoped>
.custom-wrapper{
.clock {
background: rgba(34, 34, 34, 0.8);
&::before {
content: '';
// display: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(34, 34, 34, 0.9) 20%, transparent calc(50% - .4ch), transparent calc(50% + .4ch), rgba(34, 34, 34, 0.9) 80%);
transform: translateZ(2.6em);
}
.clock-item {
--index: 0;
transform: rotateX(calc(-1turn * (var(--index) / 10)));
}
}
}
</style>
css 3D 翻页卡片数字时钟
TIP
该方案主要利用阴影、动画、css3d rotateX
<script lang='ts' setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useTimeoutPoll } from '@vueuse/core'
import { ElRadioButton, ElRadioGroup } from 'element-plus'
const hour = ref(0)
const minute = ref(0)
const second = ref(0)
const audioRef = ref()
const toggleMuted = ref('muted')
function calculTime() {
const date = new Date()
hour.value = date.getHours()
minute.value = date.getMinutes()
second.value = date.getSeconds()
}
function mapNum(num: number): string {
return num < 10 ? `0${num}` : `${num}`
}
function mapCls(numStr: string, idx: number, max: number) {
const _num = Number(numStr)
if (_num === idx) {
return 'active'
}
else {
if (_num > -1 && _num - 1 === idx)
return 'before'
if (_num === 0 && idx === max)
return 'before'
}
}
const timeStampArr = computed(() => {
return `${mapNum(hour.value)}${mapNum(minute.value)}${mapNum(second.value)}`.split('')
})
const { isActive, pause, resume } = useTimeoutPoll(calculTime, 1000)
if (!isActive.value)
resume()
onBeforeUnmount(() => {
if (isActive.value)
pause()
})
watch(() => toggleMuted.value, (val) => {
nextTick(() => {
audioRef.value.muted = val === 'muted'
})
})
watch(() => second.value, () => {
if (toggleMuted.value === 'muted')
return
nextTick(() => {
if (audioRef.value) {
setTimeout(() => {
audioRef.value.play()
}, 400)
}
})
})
</script>
<template>
<div class="custom-wrapper relative h-64 flex items-center justify-center">
<audio ref="audioRef" muted class="absolute top--100px left--100px">
<source src="../../../assets/clock-audio.mp3" type="audio/mpeg">
</audio>
<ElRadioGroup v-model="toggleMuted" class="absolute z-10 left-4 top-4" size="small">
<ElRadioButton label="muted" />
<ElRadioButton label="unMuted" />
</ElRadioGroup>
<!-- 时 -->
<div class="flip hour relative w-1ch h-1.5ch m-0.5px text-8xl text-white font-bold rounded-6px text-center mr-5px">
<div
v-for="idx in 10"
:key="`hour-0-${idx}`"
class="absolute left-0 top-0 w-full h-full"
:data-index="idx - 1"
:class="mapCls(timeStampArr[0], idx - 1, 2)"
>
<div class="up absolute left-0 top-0 w-full h-1/2 overflow-hidden origin-bottom z-1">
<div class="shadow absolute w-full h-full z-2" />
<div class="absolute left-0 bottom-0 z-5 w-full h-1px bg-#333" />
<div class="inner absolute left-0 top-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
<div class="down absolute left-0 bottom-0 w-full h-1/2 overflow-hidden origin-top z-2">
<div class="shadow absolute w-full h-full z-2" />
<div class="inner absolute left-0 bottom-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
</div>
</div>
<div class="flip hour relative w-1ch h-1.5ch m-0.5px text-8xl text-white font-bold rounded-6px text-center mr-20px">
<div
v-for="idx in 10"
:key="`hour-0-${idx}`"
class="absolute left-0 top-0 w-full h-full"
:data-index="idx - 1"
:class="mapCls(timeStampArr[1], idx - 1, 9)"
>
<div class="up absolute left-0 top-0 w-full h-1/2 overflow-hidden origin-bottom z-1">
<div class="shadow absolute w-full h-full z-2" />
<div class="absolute left-0 bottom-0 z-5 w-full h-1px bg-#333" />
<div class="inner absolute left-0 top-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
<div class="down absolute left-0 bottom-0 w-full h-1/2 overflow-hidden origin-top z-2">
<div class="shadow absolute w-full h-full z-2" />
<div class="inner absolute left-0 bottom-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
</div>
</div>
<!-- 分 -->
<div class="flip hour relative w-1ch h-1.5ch m-0.5px text-8xl text-white font-bold rounded-6px text-center mr-5px">
<div
v-for="idx in 10"
:key="`hour-0-${idx}`"
class="absolute left-0 top-0 w-full h-full"
:data-index="idx - 1"
:class="mapCls(timeStampArr[2], idx - 1, 5)"
>
<div class="up absolute left-0 top-0 w-full h-1/2 overflow-hidden origin-bottom z-1">
<div class="shadow absolute w-full h-full z-2" />
<div class="absolute left-0 bottom-0 z-5 w-full h-1px bg-#333" />
<div class="inner absolute left-0 top-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
<div class="down absolute left-0 bottom-0 w-full h-1/2 overflow-hidden origin-top z-2">
<div class="shadow absolute w-full h-full z-2" />
<div class="inner absolute left-0 bottom-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
</div>
</div>
<div class="flip hour relative w-1ch h-1.5ch m-0.5px text-8xl text-white font-bold rounded-6px text-center mr-20px">
<div
v-for="idx in 10"
:key="`hour-0-${idx}`"
class="absolute left-0 top-0 w-full h-full"
:data-index="idx - 1"
:class="mapCls(timeStampArr[3], idx - 1, 9)"
>
<div class="up absolute left-0 top-0 w-full h-1/2 overflow-hidden origin-bottom z-1">
<div class="shadow absolute w-full h-full z-2" />
<div class="absolute left-0 bottom-0 z-5 w-full h-1px bg-#333" />
<div class="inner absolute left-0 top-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
<div class="down absolute left-0 bottom-0 w-full h-1/2 overflow-hidden origin-top z-2">
<div class="shadow absolute w-full h-full z-2" />
<div class="inner absolute left-0 bottom-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
</div>
</div>
<!-- 秒 -->
<div class="flip hour relative w-1ch h-1.5ch m-0.5px text-8xl text-white font-bold rounded-6px text-center mr-5px">
<div
v-for="idx in 10"
:key="`hour-0-${idx}`"
class="absolute left-0 top-0 w-full h-full"
:data-index="idx - 1"
:class="mapCls(timeStampArr[4], idx - 1, 5)"
>
<div class="up absolute left-0 top-0 w-full h-1/2 overflow-hidden origin-bottom z-1">
<div class="shadow absolute w-full h-full z-2" />
<div class="absolute left-0 bottom-0 z-5 w-full h-1px bg-#333" />
<div class="inner absolute left-0 top-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
<div class="down absolute left-0 bottom-0 w-full h-1/2 overflow-hidden origin-top z-2">
<div class="shadow absolute w-full h-full z-2" />
<div class="inner absolute left-0 bottom-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
</div>
</div>
<div class="flip hour relative w-1ch h-1.5ch m-0.5px text-8xl text-white font-bold rounded-6px text-center mr-20px">
<div
v-for="idx in 10"
:key="`hour-0-${idx}`"
class="absolute left-0 top-0 w-full h-full"
:data-index="idx - 1"
:class="mapCls(timeStampArr[5], idx - 1, 9)"
>
<div class="up absolute left-0 top-0 w-full h-1/2 overflow-hidden origin-bottom z-1">
<div class="shadow absolute w-full h-full z-2" />
<div class="absolute left-0 bottom-0 z-5 w-full h-1px bg-#333" />
<div class="inner absolute left-0 top-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
<div class="down absolute left-0 bottom-0 w-full h-1/2 overflow-hidden origin-top z-2">
<div class="shadow absolute w-full h-full z-2" />
<div class="inner absolute left-0 bottom-0 w-full h-200% text-#ccc rounded-6px bg-#333">
{{ idx - 1 }}
</div>
</div>
</div>
</div>
</div>
</template>
<style lang='scss' scoped>
@keyframes show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes hide {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes turn {
0% {
transform: rotateX(90deg);
}
100% {
transform: rotateX(0deg);
}
}
@keyframes turn2 {
0% {
transform: rotateX(0deg);
}
100% {
transform: rotateX(-90deg);
}
}
@keyframes asd {
0% {
z-index: 2;
}
5% {
z-index: 4;
}
100% {
z-index: 4;
}
}
.custom-wrapper {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
background: rgb(150,150,150);
background: radial-gradient(ellipse at center, rgba(150,150,150, 1) 0%, rgba(89, 89, 89, 1) 100%);
.flip {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.7);
.inner {
text-shadow: 0 1px 2px #000;
}
.before {
z-index: 3;
.up {
z-index: 2;
animation: turn2 .5s linear both;
.shadow {
background: linear-gradient(to bottom, rgba(0, 0, 0, .1) 0%, rgba(0, 0, 0, 1) 100%);
animation: show .5s linear both;
}
}
.down {
.shadow {
background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, .1) 100%);
animation: show .5s linear both;
}
}
}
.active {
animation: asd 0.5s 0.5s linear both;
.down {
z-index: 2;
animation: turn 0.5s 0.5s linear both;
}
.up {
.shadow {
background: linear-gradient(to bottom, rgba(0, 0, 0, .1) 0%, rgba(0, 0, 0, 1) 100%);
animation: hide .5s .3s linear both;
}
}
.down {
.shadow {
background: linear-gradient(to bottom, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, .1) 100%);
animation: hide .5s .3s linear both;
}
}
}
}
}
</style>