先看效果图(说明:小鬼影会飘来飘去,长时间停留会有小惊喜,具体大家跑一下就知道):
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>403页面</title>
<style>
@import url('https://fonts.googleapis.com/css?family=Open+Sans|Nova+Mono');
:root {
--font-header: 'Nova Mono', monospace;
--font-text: 'Open Sans', sans-serif;
--color-theme: #F1EEDB;
--color-bg: #282B24;
--animation-sentence: '你知道你应该离开,对吧?';
--animation-duration: 40s;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
width: 100%;
font-family: var(--font-text);
color: var(--color-theme);
background: var(--color-bg);
overflow: hidden;
}
.container {
text-align: center;
margin: 1rem 0.5rem 0;
}
.container h1 {
font-family: var(--font-header);
font-size: calc(4rem + 2vw);
text-transform: uppercase;
}
.container p {
text-transform: uppercase;
letter-spacing: 0.2rem;
font-size: 2rem;
margin: 1.5rem 0 3rem;
}
svg.keyhole {
height: 82px;
width: 82px;
opacity: 0;
visibility: hidden;
/* 为钥匙孔定义一个动画,以引入默认情况下暂停的动画,在JavaScript中超时运行*/
animation: showKey 0.5s 0.5s paused ease-out forwards;
}
svg.key {
height: 164px;
width: 164px;
position: absolute;
opacity: 0;
visibility: hidden;
/* 为钥匙孔定义一个动画,以引入默认情况下暂停的动画,在JavaScript中超时运行*/
animation: showKey 0.5s 0.5s paused ease-out forwards;
}
.ghost {
/* border: 1px solid tomato; */
position: absolute;
bottom: 5px;
left: calc(50% - 100px);
width: 200px;
height: 200px;
/* 让鬼影移动到屏幕的右侧和左侧,转到其中心位置并重复动画两次 */
animation: hoverGhost calc(var(--animation-duration)/2) ease-in-out 2;
}
/* 通过连接到动画div的伪元素引入文本 */
.ghost:before {
content: var(--animation-sentence);
color: var(--color-theme);
border-radius: 50%;
position: absolute;
bottom: 100%;
text-align: center;
line-height: 2;
padding: 1rem;
visibility: hidden;
opacity: 0;
/* 当鬼影从屏幕右边缘返回时,引入每一个文本字符串,以及覆盖中心部分所需的时间长度(第四个字符串,由于动画长度是总持续时间的一半,因此变为八个字符串)
/* 假设持续时间为40秒的情况下,第一个延迟为7.5秒,第二个延迟为27.5秒,最后一个延迟为40秒,经过运算,可以归结为316、2740和1
// 记得在钥匙和钥匙孔的动画中加入一点延迟
*/
animation:
showText calc(var(--animation-duration)/8) calc(var(--animation-duration)*3/16) ease-out forwards,
showNewText calc(var(--animation-duration)/8) calc(var(--animation-duration)*27/40) ease-out forwards,
showFinalText calc(var(--animation-duration)/8) var(--animation-duration) ease-out forwards;
}
/* 定义关键帧动画-悬停鬼影使鬼影向右、向左移动,然后返回到其默认位置
-showKey将钥匙(和钥匙孔)svg引入视图
-showText、showNewText、showFinalText显示不同的字符串*/
@keyframes hoverGhost {
25% {
transform: translateX(20vw);
}
75% {
transform: translateX(-20vw);
}
}
@keyframes showKey {
to {
opacity: 1;
visibility: visible;
}
}
/* 更改文本,更改自定义属性的值,厌倦了在隐藏伪元素时更改其值,以及在最后一个关键帧中更改其值(因为动画根据填充模式属性的“向前”值给出该值)*/
@keyframes showText {
2% {
opacity: 1;
visibility: visible;
}
98% {
opacity: 1;
visibility: visible;
}
99% {
--animation-sentence: '你知道你应该离开,对吧?';
opacity: 0;
visibility: hidden;
}
100% {
--animation-sentence: '这么多事情要做,这么少时间...';
}
}
@keyframes showNewText {
2% {
--animation-sentence: '这么多事情要做,这么少时间...';
opacity: 1;
visibility: visible;
}
98% {
opacity: 1;
visibility: visible;
}
99% {
--animation-sentence: '这么多事情要做,这么少时间...';
opacity: 0;
visibility: hidden;
}
100% {
--animation-sentence: '好吧,你似乎很关心这个.这是一把只给你的钥匙..';
}
}
@keyframes showFinalText {
2% {
opacity: 1;
visibility: visible;
}
98% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
</style>
</head>
<body>
<!-- 包括在项目中使用的svg -->
<svg style="display: none;">
<symbol id="keyhole" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 26.458333 26.458334"><g transform="translate(0 -270.542)"><circle cx="13.229" cy="279.141" r="8.599" fill="#f1eedb" paint-order="stroke fill markers"/><path d="M10.516 283.271h5.427c1.164 0 1.768.861 2.102 1.802l3.59 10.125c.334.94-.937 1.802-2.102 1.802H6.926c-1.165 0-2.437-.861-2.103-1.802l3.59-10.125c.334-.94.938-1.802 2.103-1.802z" fill="#f1eedb" paint-order="stroke fill markers"/><circle r="6.06" cy="279.141" cx="13.229" fill="#282b24" paint-order="stroke fill markers"/><path d="M11.502 283.76h3.455c.741 0 1.126.733 1.338 1.534l2.286 8.614c.213.8-.597 1.534-1.338 1.534H9.216c-.742 0-1.551-.733-1.339-1.534l2.286-8.614c.212-.8.597-1.534 1.339-1.534z" fill="#282b24" paint-order="stroke fill markers"/></g></symbol>
<symbol id="key" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 26.458333 26.458334"><circle cx="13.229" cy="279.141" r="8.599" paint-order="stroke fill markers" transform="matrix(0 -.76923 .7499 0 -202.882 23.405)" fill="#f1eedb"/><circle r="8.599" cy="279.141" cx="13.229" paint-order="stroke fill markers" transform="matrix(0 -.5887 .57392 0 -153.756 21.017)" fill="#282b24"/><path fill="#f1eedb" paint-order="stroke fill markers" d="M12.03 12.13h14.428v2.2H12.03z"/><path fill="#f1eedb" paint-order="stroke fill markers" d="M18.147 12.13h2.895v6.772h-2.895zM22.113 12.13h2.716v5.065h-2.716z"/></symbol>
<symbol id="ghost" xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 26.458333 26.458334"><g transform="translate(0 -270.542)"><path d="M4.63 279.293c0-4.833 3.85-8.751 8.6-8.751 4.748 0 8.598 3.918 8.598 8.75H13.23zM4.725 279.293h16.914c.052 0 .19.043.19.096l-.095 14.329c0 .026-.011.05-.028.068a.093.093 0 0 1-.067.028c-.881 0-1.235-1.68-2.114-1.616-.995.072-1.12 2.082-2.114 2.154-.88.064-1.233-1.615-2.115-1.615-.881 0-1.233 1.615-2.114 1.615-.881 0-1.233-1.615-2.114-1.615-.882 0-1.236 1.679-2.115 1.615-.994-.072-1.12-2.082-2.114-2.154-.88-.063-1.41 1.077-2.114 1.616-.021.016-.05-.01-.067-.028a.097.097 0 0 1-.028-.068v-14.33c0-.052.042-.095.095-.095z" fill="#f1eedb" paint-order="stroke fill markers"/><path d="M15.453 281.27a1.987 1.94 0 0 1-.994 1.68 1.987 1.94 0 0 1-1.987 0 1.987 1.94 0 0 1-.994-1.68h1.988z" fill="#282b24" paint-order="stroke fill markers"/><g fill="#282b24" transform="matrix(1 0 0 1.0177 .283 -5.653)"><ellipse cx="10.205" cy="278.668" rx="1.231" ry="1.181" paint-order="stroke fill markers"/><ellipse ry="1.181" rx="1.231" cy="278.668" cx="16.159" paint-order="stroke fill markers"/><ellipse ry=".331" rx=".853" cy="280.936" cx="10.205" opacity=".5" paint-order="stroke fill markers"/><ellipse cx="16.159" cy="280.936" rx=".853" ry=".331" opacity=".5" paint-order="stroke fill markers"/></g><ellipse ry=".614" rx="8.082" cy="296.386" cx="13.229" opacity=".1" fill="#f1eedb" paint-order="stroke fill markers"/></g></symbol>
</svg>
<!-- 在一个容器中包括一个标题、段落和锁眼的svg -->
<div class="container">
<h1>403</h1>
<p>access not granted</p>
<svg class="keyhole">
<use href="#keyhole"/>
</svg>
</div>
<!-- 在容器外部,使它们相对于主体绝对定位,包括一个用于钥匙的svg和一个用于鬼影的svg -->
<svg class="key">
<use href="#key"/>
</svg>
<!--
将svg嵌套在div中,为svg和div提供相同的类div和svg在通过transform属性转换元素时表现不同,
从而在文本(包含在div上的伪元素中)和svg之间提供了很好的距离
-->
<div class="ghost">
<svg class="ghost">
<use href="#ghost"/>
</svg>
</div>
</body>
<script>
// 以项目中使用的DOM中的元素为目标
/**
* 钥匙和钥匙孔的svg
* 标题和段落
*/
const key = document.querySelector(".key");
const keyhole = document.querySelector(".keyhole");
const ghost = document.querySelector(".ghost");
const heading = document.querySelector("h1");
const paragraph = document.querySelector("p");
// 对于timout的长度,请考虑--animation-duration自定义属性,并在根元素上添加一个小延迟检索属性
const root = document.querySelector(":root");
const rootStyles = getComputedStyle(root);
// 检索动画持续时间自定义属性
// 这被指定为“40s”,以秒为单位,因此解析数字并以毫秒为单位将其包括在内
const animationDuration = parseInt(rootStyles.getPropertyValue("--animation-duration"))*1000;
let keyTimer = animationDuration*9/8;
// 检索钥匙的尺寸(使钥匙正好位于光标所在的位置)
const keyBox = key.getBoundingClientRect();
// console.log(keyBox);
// 钥匙和钥匙孔动画
// 在指定的时间范围内包括超时
const timeoutID = setTimeout(() => {
// 在指定的时间后,将光标更改为似乎抓住了钥匙
key.parentElement.parentElement.style.cursor = "grab";
// 通过触发默认情况下暂停的动画,引入钥匙和钥匙孔svg元素
key.style.animationPlayState = "running";
keyhole.style.animationPlayState = "running";
// 将钥匙上的指针事件设置为none,以允许在钥匙孔上发生鼠标悬停事件
// 钥匙实际上是代替普通光标使用的,并且会重叠在所有内容的顶部
key.style.pointerEvents = "none";
// 当光标悬停在窗口中的任何位置时,调用一个函数来更新钥匙的位置并使其与光标匹配
window.addEventListener("mousemove", updateKeyPosition);
// 当光标悬停在钥匙孔上时,调用一个函数来授予访问权限并删除当前侦听器
keyhole.addEventListener("mouseover", grantAccess);
clearTimeout(timeoutID);
}, keyTimer);
// 定义根据鼠标坐标(以及钥匙自身的尺寸)更新绝对定位钥匙的位置的功能
const updateKeyPosition = (e) => {
let x = e.clientX;
let y = e.clientY;
key.style.left = x - keyBox.width/1.5;
key.style.top = y - keyBox.height/2;
};
// 定义通知用户授予访问权限的功能
const grantAccess = () => {
// 恢复光标
key.parentElement.parentElement.style.cursor = "default";
// 更改标题和段落元素的文本
heading.textContent = '🎉 yay 🎉';
paragraph.textContent = 'access granted';
// 从文档流中删除钥匙和钥匙孔的svg元素
keyhole.style.display = "none";
key.style.display = "none";
// 删除事件侦听器,尤其是窗口上的侦听器
window.removeEventListener("mousemove", updateKeyPosition);
keyhole.removeEventListener("mouseover", grantAccess);
};
</script>
</html>
PS:发现我用文字写太生硬了,干的噎嗓子,干脆在代码里加注释了。