feat(工具箱): 完善“原神时钟”,添加动画效果,支持旋转指针
This commit is contained in:
163
src/views/ToolboxView/Other/GenshinImpactClock/ClockColor.vue
Normal file
163
src/views/ToolboxView/Other/GenshinImpactClock/ClockColor.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<svg
|
||||
class="clock-color"
|
||||
:viewBox="`0 0 ${elSize} ${elSize}`"
|
||||
>
|
||||
|
||||
<!-- 定义 -->
|
||||
<defs>
|
||||
<!-- 背景图案 -->
|
||||
<pattern
|
||||
id="color-pattern"
|
||||
x="0"
|
||||
y="0"
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternContentUnits="userSpaceOnUse"
|
||||
:width="elSize"
|
||||
:height="elSize"
|
||||
>
|
||||
<image
|
||||
:width="elSize"
|
||||
:height="elSize"
|
||||
:href="IMAGE_TIME_ZONE_COLOR"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- 外层圆环 -->
|
||||
<path
|
||||
class="color-circle"
|
||||
:class="{ faded: currAngle > 360 }"
|
||||
:d="state.dOuter"
|
||||
fill="url(#color-pattern)"
|
||||
fill-rule="evenodd"
|
||||
></path>
|
||||
|
||||
<!-- 内层圆环 -->
|
||||
<path
|
||||
ref="innerCircle"
|
||||
class="color-circle"
|
||||
:d="state.dInner"
|
||||
fill="url(#color-pattern)"
|
||||
fill-rule="evenodd"
|
||||
></path>
|
||||
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
reactive, watch,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
IMAGE_TIME_ZONE_COLOR,
|
||||
} from './common-data';
|
||||
|
||||
import arc from '@/assets/js/svg-arc';
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
elSize: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
|
||||
currAngle: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
startAngle: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
|
||||
thickness: {
|
||||
type: Number,
|
||||
default: 8,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
dInner: '',
|
||||
dOuter: '',
|
||||
});
|
||||
|
||||
const halfSize = computed(() => {
|
||||
return (props.elSize / 2);
|
||||
});
|
||||
|
||||
// 角度变化时更新状态
|
||||
watch(() => (props.currAngle), () => {
|
||||
updateCircle();
|
||||
});
|
||||
watch(() => (props.startAngle), () => {
|
||||
updateCircle();
|
||||
});
|
||||
|
||||
/** 更新圆环状态 */
|
||||
function updateCircle() {
|
||||
|
||||
const { currAngle, startAngle, thickness } = props;
|
||||
|
||||
const radius = halfSize.value;
|
||||
const size = thickness;
|
||||
const offset = size / 4;
|
||||
|
||||
const endAngleInner = Math.max(0, currAngle - 360) + startAngle;
|
||||
const endAngleOuter = Math.min(360, currAngle) + startAngle;
|
||||
|
||||
// 内层圆环
|
||||
state.dInner = arc({
|
||||
x: radius,
|
||||
y: radius,
|
||||
R: radius - size * 2 - offset,
|
||||
r: radius - size - offset,
|
||||
start: startAngle,
|
||||
end: endAngleInner,
|
||||
});
|
||||
|
||||
// 外层圆环
|
||||
state.dOuter = arc({
|
||||
x: radius,
|
||||
y: radius,
|
||||
R: radius - size,
|
||||
r: radius,
|
||||
start: startAngle,
|
||||
end: endAngleOuter,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateCircle();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.clock-color {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.color-circle {
|
||||
filter: brightness(1);
|
||||
transition: filter 0.25s;
|
||||
}
|
||||
|
||||
.faded {
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
}
|
||||
</style>
|
603
src/views/ToolboxView/Other/GenshinImpactClock/ClockElement.vue
Normal file
603
src/views/ToolboxView/Other/GenshinImpactClock/ClockElement.vue
Normal file
@@ -0,0 +1,603 @@
|
||||
<template>
|
||||
<div
|
||||
ref="clockElement"
|
||||
:class="{
|
||||
'clock-element': true,
|
||||
'clock-rotation': clockState.isRotation,
|
||||
}"
|
||||
:style="elStyle"
|
||||
>
|
||||
|
||||
<!-- 外部 -->
|
||||
<div class="clock-outer bg-contain"></div>
|
||||
|
||||
<!-- 内部 -->
|
||||
<div class="clock-inner">
|
||||
|
||||
<!-- 背景 -->
|
||||
<div class="inner-bg bg-cover"></div>
|
||||
<div class="inner-star bg-cover"></div>
|
||||
|
||||
<!-- 齿轮 -->
|
||||
<div class="clock-gear">
|
||||
<div class="gear-6"></div>
|
||||
<div class="gear-5"></div>
|
||||
<div class="gear-4"></div>
|
||||
<div class="gear-3"></div>
|
||||
<div class="gear-2"></div>
|
||||
<div class="gear-1"></div>
|
||||
</div>
|
||||
|
||||
<!-- 色环 -->
|
||||
<clock-color
|
||||
:curr-angle="upperRealAngle"
|
||||
:start-angle="lowerPointer.viewAngle"
|
||||
/>
|
||||
|
||||
<!-- 下层指针 -->
|
||||
<div class="pointer-wrapper pointer-lower bg-contain">
|
||||
<div class="pointer-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- 上层指针 -->
|
||||
<div class="pointer-wrapper pointer-upper bg-contain">
|
||||
<div
|
||||
class="pointer-content"
|
||||
@mousedown="handleDragPointer('upper')"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 表盘 -->
|
||||
<div class="clock-dial bg-contain">
|
||||
<div class="time-icons">
|
||||
<div class="time-morning bg-contain"></div>
|
||||
<div class="time-noon bg-contain"></div>
|
||||
<div class="time-dusk bg-contain"></div>
|
||||
<div class="time-night bg-contain"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
onBeforeUnmount, onMounted,
|
||||
reactive, ref,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
IMAGE_CLOCK_BG_INNER,
|
||||
IMAGE_CLOCK_BG_OUTER,
|
||||
IMAGE_CLOCK_DIAL,
|
||||
IMAGE_CLOCK_GEAR_1,
|
||||
IMAGE_CLOCK_GEAR_4,
|
||||
IMAGE_CLOCK_GEAR_5,
|
||||
IMAGE_CLOCK_GEAR_6,
|
||||
IMAGE_CLOCK_PARTICLES,
|
||||
IMAGE_POINTER_LOWER,
|
||||
IMAGE_POINTER_UPPER,
|
||||
IMAGE_TIME_ICON_DUSK,
|
||||
IMAGE_TIME_ICON_MORNING,
|
||||
IMAGE_TIME_ICON_NIGHT,
|
||||
IMAGE_TIME_ICON_NOON,
|
||||
} from './common-data';
|
||||
|
||||
import ClockColor from './ClockColor.vue';
|
||||
|
||||
const clockElement = ref(null);
|
||||
|
||||
/** 时钟状态 */
|
||||
const clockState = reactive({
|
||||
|
||||
/** 指针是否正在旋转 */
|
||||
isRotation: false,
|
||||
|
||||
/** 最新一次获取到的指针角度 */
|
||||
lastAngle: 0,
|
||||
|
||||
/** 定时器 ID */
|
||||
rotationWatcher: null,
|
||||
|
||||
});
|
||||
|
||||
/** 下层指针状态 */
|
||||
const lowerPointer = reactive({
|
||||
|
||||
/** 视图角度 */
|
||||
viewAngle: 60,
|
||||
|
||||
});
|
||||
|
||||
/** 上层指针状态 */
|
||||
const upperPointer = reactive({
|
||||
|
||||
/** 是否为第二圈 */
|
||||
isSecond: false,
|
||||
|
||||
/** 实际角度 */
|
||||
dataAngle: 0,
|
||||
|
||||
/** 视图角度 */
|
||||
viewAngle: 60,
|
||||
|
||||
});
|
||||
|
||||
/** 元素 CSS */
|
||||
const elStyle = computed(() => {
|
||||
return {
|
||||
'--image-clock-bg-inner': IMAGE_CLOCK_BG_INNER,
|
||||
'--image-clock-bg-outer': IMAGE_CLOCK_BG_OUTER,
|
||||
'--image-clock-dial': IMAGE_CLOCK_DIAL,
|
||||
'--image-clock-gear-1': IMAGE_CLOCK_GEAR_1,
|
||||
'--image-clock-gear-4': IMAGE_CLOCK_GEAR_4,
|
||||
'--image-clock-gear-5': IMAGE_CLOCK_GEAR_5,
|
||||
'--image-clock-gear-6': IMAGE_CLOCK_GEAR_6,
|
||||
'--image-clock-particles': IMAGE_CLOCK_PARTICLES,
|
||||
'--image-pointer-lower': IMAGE_POINTER_LOWER,
|
||||
'--image-pointer-upper': IMAGE_POINTER_UPPER,
|
||||
'--image-time-icon-dusk': IMAGE_TIME_ICON_DUSK,
|
||||
'--image-time-icon-morning': IMAGE_TIME_ICON_MORNING,
|
||||
'--image-time-icon-night': IMAGE_TIME_ICON_NIGHT,
|
||||
'--image-time-icon-noon': IMAGE_TIME_ICON_NOON,
|
||||
'--pointer-lower-angle': `${lowerPointer.viewAngle}deg`,
|
||||
'--pointer-upper-angle': `${upperPointer.viewAngle}deg`,
|
||||
};
|
||||
});
|
||||
|
||||
/** 上层指针实际已旋转角度 */
|
||||
const upperRealAngle = computed(() => {
|
||||
const { dataAngle, isSecond } = upperPointer;
|
||||
return (isSecond ? dataAngle + 360 : dataAngle);
|
||||
});
|
||||
|
||||
/** 初始化定时器 */
|
||||
function timerInit() {
|
||||
|
||||
// 指针旋转检测
|
||||
clockState.rotationWatcher = setInterval(function () {
|
||||
const currAngle = upperRealAngle.value;
|
||||
const lastAngle = clockState.lastAngle;
|
||||
clockState.isRotation = (Math.abs(currAngle - lastAngle) >= 5);
|
||||
clockState.lastAngle = currAngle;
|
||||
}, 250);
|
||||
|
||||
}
|
||||
|
||||
/** 重置定时器 */
|
||||
function timerReset() {
|
||||
clearInterval(clockState.rotationWatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取元素中心坐标
|
||||
* @param {HTMLElement} el
|
||||
*/
|
||||
function getCenterPoint(el) {
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理拖拽上层指针 */
|
||||
function handleDragPointer() {
|
||||
|
||||
const center = getCenterPoint(clockElement.value);
|
||||
|
||||
if (!center) {
|
||||
return;
|
||||
}
|
||||
|
||||
const centerX = center.x;
|
||||
const centerY = center.y;
|
||||
|
||||
// 节流
|
||||
let last = 0;
|
||||
|
||||
document.onmousemove = function (ev) {
|
||||
|
||||
let curr = Date.now();
|
||||
|
||||
if (curr - last >= 20) {
|
||||
last = curr;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
let { pageX, pageY } = ev;
|
||||
|
||||
let numX = pageX - centerX;
|
||||
let numY = pageY - centerY;
|
||||
|
||||
// 计算两点弧度 & 转换为角度(-180 ~ 180)
|
||||
let calcAngle = Math.round(Math.atan2(numY, numX) * (180 / Math.PI));
|
||||
|
||||
// 转换为视图角度(0 ~ 359)
|
||||
let viewAngle = calcAngle + (calcAngle >= -90 ? 90 : 450);
|
||||
|
||||
// 用于数据处理的角度
|
||||
let dataAngle = 0;
|
||||
|
||||
// 起始角度偏移值
|
||||
let offsetAngle = lowerPointer.viewAngle;
|
||||
|
||||
// 处理偏移,获取数据角度
|
||||
if (viewAngle >= offsetAngle) {
|
||||
dataAngle = viewAngle - offsetAngle;
|
||||
} else if (viewAngle < offsetAngle) {
|
||||
dataAngle = viewAngle + (360 - offsetAngle);
|
||||
}
|
||||
|
||||
// 新角度与原角度的差值
|
||||
let diff = dataAngle - upperPointer.dataAngle;
|
||||
|
||||
// 顺时针越过起始点
|
||||
if (diff <= -180) {
|
||||
if (upperPointer.isSecond) {
|
||||
// 当前为第二圈,阻止移动
|
||||
dataAngle = 360;
|
||||
viewAngle = 360 + offsetAngle;
|
||||
} else {
|
||||
// 当前为第一圈,进入第二圈
|
||||
upperPointer.isSecond = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 逆时针越过起始点
|
||||
if (diff >= 180) {
|
||||
if (upperPointer.isSecond) {
|
||||
// 当前为第二圈,返回第一圈
|
||||
upperPointer.isSecond = false;
|
||||
} else {
|
||||
// 当前为第一圈,阻止移动
|
||||
dataAngle = 0;
|
||||
viewAngle = offsetAngle;
|
||||
}
|
||||
}
|
||||
|
||||
upperPointer.dataAngle = dataAngle;
|
||||
upperPointer.viewAngle = viewAngle;
|
||||
|
||||
};
|
||||
|
||||
document.onmouseup = function () {
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/** 确定当前选择的时间,下层指针旋转至上层指针的位置 */
|
||||
function handleSubmitTime() {
|
||||
|
||||
let lowerAngleStart = lowerPointer.viewAngle;
|
||||
let upperAngleStart = upperRealAngle.value;
|
||||
let upperAngleCurr = upperAngleStart;
|
||||
let timer = setInterval(() => {
|
||||
|
||||
// 每次 -2,最小值为 0
|
||||
upperAngleCurr = Math.max(0, upperAngleCurr - 2);
|
||||
|
||||
// 结束
|
||||
if (upperAngleCurr === 0) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
|
||||
let upperAngleDiff = upperAngleStart - upperAngleCurr;
|
||||
let lowerAngleCurr = lowerAngleStart + upperAngleDiff;
|
||||
|
||||
lowerPointer.viewAngle = lowerAngleCurr % 360;
|
||||
upperPointer.dataAngle = upperAngleCurr % 360;
|
||||
upperPointer.isSecond = (upperAngleCurr >= 360);
|
||||
|
||||
}, 50);
|
||||
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleSubmitTime,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
timerInit();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
timerReset();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.bg-contain {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.bg-cover {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.clock-element {
|
||||
--pointer-lower-angle: 0deg;
|
||||
--pointer-upper-angle: 0deg;
|
||||
position: relative;
|
||||
width: 32em;
|
||||
height: 32em;
|
||||
border-radius: 50%;
|
||||
font-size: 1rem;
|
||||
overflow: hidden;
|
||||
filter: brightness(1.1) saturate(0.9);
|
||||
}
|
||||
|
||||
.clock-outer {
|
||||
position: absolute;
|
||||
top: -1%;
|
||||
left: -1%;
|
||||
width: 102%;
|
||||
height: 102%;
|
||||
background-image: var(--image-clock-bg-outer);
|
||||
}
|
||||
|
||||
.clock-inner {
|
||||
--size: 46%;
|
||||
position: absolute;
|
||||
top: calc(50% - var(--size) / 2);
|
||||
left: calc(50% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 50%;
|
||||
background-color: #000;
|
||||
overflow: hidden;
|
||||
|
||||
.inner-bg, .inner-star {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.inner-bg {
|
||||
background-image: var(--image-clock-bg-inner);
|
||||
animation: rotation-backward 60s linear infinite;
|
||||
}
|
||||
|
||||
.inner-star {
|
||||
background-image: var(--image-clock-particles);
|
||||
}
|
||||
}
|
||||
|
||||
.clock-gear {
|
||||
--size: 120%;
|
||||
--ratio: 1.55;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.6;
|
||||
|
||||
> div {
|
||||
--ani-duration: 2s;
|
||||
position: absolute;
|
||||
animation-duration: var(--ani-duration);
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
transform: rotate(0);
|
||||
will-change: transform;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
animation-duration: calc(var(--ani-duration) / 12);
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: inherit;
|
||||
animation-play-state: paused;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
}
|
||||
|
||||
.gear-1 {
|
||||
--ani-duration: calc(1s * var(--gear-teeth) * var(--ratio));
|
||||
--gear-teeth: 32;
|
||||
top: calc(50% - var(--size) / 2);
|
||||
left: calc(50% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
animation-name: rotation-backward;
|
||||
|
||||
&::after {
|
||||
background-image: var(--image-clock-gear-1);
|
||||
}
|
||||
}
|
||||
|
||||
.gear-2 {
|
||||
--ani-duration: calc(1s * var(--gear-teeth) * var(--ratio));
|
||||
--gear-teeth: 32;
|
||||
top: calc(0% - var(--size) / 2);
|
||||
left: calc(72% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
animation-name: rotation-backward;
|
||||
|
||||
&::after {
|
||||
background-image: var(--image-clock-gear-1);
|
||||
}
|
||||
}
|
||||
|
||||
.gear-3 {
|
||||
--ani-duration: calc(1s * var(--gear-teeth) * var(--ratio));
|
||||
--gear-teeth: 32;
|
||||
top: calc(77% - var(--size) / 2);
|
||||
left: calc(100% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
animation-name: rotation-backward;
|
||||
opacity: 0.5;
|
||||
|
||||
&::after {
|
||||
background-image: var(--image-clock-gear-1);
|
||||
}
|
||||
}
|
||||
|
||||
.gear-4 {
|
||||
--angle-offset: 4deg;
|
||||
--ani-duration: calc(1s * var(--gear-teeth) * var(--ratio));
|
||||
--gear-teeth: 32;
|
||||
top: calc(77% - var(--size) / 2);
|
||||
left: calc(100% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
animation-name: rotation-forward;
|
||||
|
||||
&::after {
|
||||
background-image: var(--image-clock-gear-4);
|
||||
}
|
||||
}
|
||||
|
||||
// 正常:约 74s / 圈
|
||||
// 加速:约 7s / 圈
|
||||
.gear-5 {
|
||||
--ani-duration: calc(1s * (var(--gear-teeth) - 1) * var(--ratio));
|
||||
--gear-teeth: 49;
|
||||
top: calc(60% - var(--size) / 2);
|
||||
left: calc(33% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
animation-name: rotation-backward;
|
||||
|
||||
&::after {
|
||||
background-image: var(--image-clock-gear-5);
|
||||
}
|
||||
}
|
||||
|
||||
.gear-6 {
|
||||
--ani-duration: calc(1s * var(--gear-teeth) * var(--ratio));
|
||||
--gear-teeth: 49;
|
||||
top: calc(42% - var(--size) / 2);
|
||||
left: calc(62% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
animation-name: rotation-backward;
|
||||
|
||||
&::after {
|
||||
background-image: var(--image-clock-gear-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 齿轮加速转动
|
||||
.clock-rotation .clock-gear > div::after {
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
.pointer-wrapper {
|
||||
--size: 180%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
&.pointer-lower {
|
||||
background-image: var(--image-pointer-lower);
|
||||
transform: translate(-50%, -50%) rotate(var(--pointer-lower-angle));
|
||||
}
|
||||
|
||||
&.pointer-upper {
|
||||
background-image: var(--image-pointer-upper);
|
||||
transform: translate(-50%, -50%) rotate(var(--pointer-upper-angle));
|
||||
}
|
||||
|
||||
.pointer-content {
|
||||
--width: 5%;
|
||||
position: absolute;
|
||||
left: calc(50% - var(--width) / 2);
|
||||
width: var(--width);
|
||||
height: 50%;
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
|
||||
.clock-dial {
|
||||
--size: 88%;
|
||||
position: absolute;
|
||||
top: calc(50% - var(--size) / 2);
|
||||
left: calc(50% - var(--size) / 2);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
background-image: var(--image-clock-dial);
|
||||
pointer-events: none;
|
||||
|
||||
.time-icons {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
// transition: opacity 1s;
|
||||
}
|
||||
|
||||
.time-morning {
|
||||
background-image: var(--image-time-icon-morning);
|
||||
}
|
||||
|
||||
.time-noon {
|
||||
background-image: var(--image-time-icon-noon);
|
||||
}
|
||||
|
||||
.time-dusk {
|
||||
background-image: var(--image-time-icon-dusk);
|
||||
}
|
||||
|
||||
.time-night {
|
||||
background-image: var(--image-time-icon-night);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 顺时针旋转
|
||||
@keyframes rotation-forward {
|
||||
0% {
|
||||
transform: rotate(calc(0deg + var(--angle-offset, 0deg)));
|
||||
}
|
||||
100% {
|
||||
transform: rotate(calc(360deg + var(--angle-offset, 0deg)));
|
||||
}
|
||||
}
|
||||
|
||||
// 逆时针旋转
|
||||
@keyframes rotation-backward {
|
||||
0% {
|
||||
transform: rotate(calc(360deg + var(--angle-offset, 0deg)));
|
||||
}
|
||||
100% {
|
||||
transform: rotate(calc(0deg + var(--angle-offset, 0deg)));
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="genshin-button">
|
||||
<span
|
||||
v-if="hasIcon"
|
||||
class="btn-icon mdi"
|
||||
:class="iconName"
|
||||
:style="{ color: iconColor }"
|
||||
></span>
|
||||
<span class="btn-label">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
|
||||
hasIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF',
|
||||
},
|
||||
|
||||
iconName: {
|
||||
type: String,
|
||||
default: 'mdi-help',
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.genshin-button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin: 1rem 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 1.5rem;
|
||||
background-color: #ECE3D6;
|
||||
color: #494246;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
opacity: 1;
|
||||
transition: opacity 0.25s;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.25rem;
|
||||
border-radius: 50%;
|
||||
background-color: #2D2D2D;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.btn-label {
|
||||
display: inline-block;
|
||||
padding: 0 3.5rem;
|
||||
}
|
||||
</style>
|
@@ -1,9 +1,50 @@
|
||||
<template>
|
||||
<div class="tool-detail-page"></div>
|
||||
<div class="tool-detail-page">
|
||||
|
||||
<clock-element ref="clockRef" />
|
||||
|
||||
<!-- <genshin-button
|
||||
icon-color="#F44336"
|
||||
icon-name="mdi-close"
|
||||
>取消</genshin-button> -->
|
||||
|
||||
<genshin-button
|
||||
icon-color="#FFC107"
|
||||
icon-name="mdi-circle-outline"
|
||||
@click="handleConfirm"
|
||||
>确认</genshin-button>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import ClockElement from './ClockElement.vue';
|
||||
import GenshinButton from './GenshinButton.vue';
|
||||
|
||||
/**
|
||||
* @desc 时钟元素 ref
|
||||
* @type {VueRef<InstanceType<ClockElement>>}
|
||||
*/
|
||||
const clockRef = ref(null);
|
||||
|
||||
/** 处理点击确认按钮 */
|
||||
function handleConfirm() {
|
||||
|
||||
let el = clockRef.value;
|
||||
|
||||
if (el) {
|
||||
el.handleSubmitTime();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tool-detail-page {
|
||||
background-color: #000;
|
||||
}
|
||||
</style>
|
||||
|
@@ -0,0 +1,16 @@
|
||||
export const IMAGE_BASE = `https://c.frost-zx.top/data/static/image/genshin-impact-clock`;
|
||||
export const IMAGE_CLOCK_BG_INNER = `url("${IMAGE_BASE}/clock_bg_inner.png")`;
|
||||
export const IMAGE_CLOCK_BG_OUTER = `url("${IMAGE_BASE}/clock_bg_outer.png")`;
|
||||
export const IMAGE_CLOCK_DIAL = `url("${IMAGE_BASE}/clock_dial.png")`;
|
||||
export const IMAGE_CLOCK_GEAR_1 = `url("${IMAGE_BASE}/clock_gear_1.png")`;
|
||||
export const IMAGE_CLOCK_GEAR_4 = `url("${IMAGE_BASE}/clock_gear_4.png")`;
|
||||
export const IMAGE_CLOCK_GEAR_5 = `url("${IMAGE_BASE}/clock_gear_5.png")`;
|
||||
export const IMAGE_CLOCK_GEAR_6 = `url("${IMAGE_BASE}/clock_gear_6.png")`;
|
||||
export const IMAGE_CLOCK_PARTICLES = `url("${IMAGE_BASE}/clock_particles.gif")`;
|
||||
export const IMAGE_POINTER_LOWER = `url("${IMAGE_BASE}/pointer_lower.png")`;
|
||||
export const IMAGE_POINTER_UPPER = `url("${IMAGE_BASE}/pointer_upper.png")`;
|
||||
export const IMAGE_TIME_ICON_DUSK = `url("${IMAGE_BASE}/time_icon_dusk.png")`;
|
||||
export const IMAGE_TIME_ICON_MORNING = `url("${IMAGE_BASE}/time_icon_morning.png")`;
|
||||
export const IMAGE_TIME_ICON_NIGHT = `url("${IMAGE_BASE}/time_icon_night.png")`;
|
||||
export const IMAGE_TIME_ICON_NOON = `url("${IMAGE_BASE}/time_icon_noon.png")`;
|
||||
export const IMAGE_TIME_ZONE_COLOR = `${IMAGE_BASE}/time_zone_color.png`;
|
Reference in New Issue
Block a user