<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>一个有趣的鬼魂动画</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="scene-container stars-out" tabindex="1">
<div class="ghost-container">
<div class="ghost">
<div class="ghost-head">
<div class="ghost-face">
<div class="eyes-row">
<div class="eye left"></div>
<div class="eye right"></div>
</div>
<div class="mouth-row">
<div class="cheek left"></div>
<div class="mouth">
<div class="mouth-top"></div>
<div class="mouth-bottom"></div>
</div>
<div class="cheek right"></div>
</div>
</div>
</div>
<div class="ghost-body">
<div class="ghost-hand hand-left"></div>
<div class="ghost-hand hand-right"></div>
<div class="ghost-skirt">
<div class="pleat down"></div>
<div class="pleat up"></div>
<div class="pleat down"></div>
<div class="pleat up"></div>
<div class="pleat down"></div>
</div>
</div>
</div>
<div class="stars">
<div class="star orange pointy star-1"><div class="star-element"></div></div>
<div class="star orange pointy star-2"><div class="star-element"></div></div>
<div class="star yellow pointy star-3"><div class="star-element"></div></div>
<div class="star yellow pointy star-4"><div class="star-element"></div></div>
<div class="star blue round star-5"><div class="star-element"></div></div>
<div class="star blue round star-6"><div class="star-element"></div></div>
</div>
</div>
<div class="shadow-container">
<div class="shadow"></div>
<div class="shadow-bottom"></div>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
class Ghost {
constructor(el) {
this.scene = el;
this.clone = el.cloneNode(true);
this.isEscaping = false;
this.ghost = el.querySelector('.ghost');
this.face = el.querySelector('.ghost-face');
this.eyes = el.querySelector('.eyes-row');
this.leftEye = this.eyes.querySelector('.left');
this.rightEye = this.eyes.querySelector('.right');
this.mouth = el.querySelector('.mouth');
this.mouthState = 'neutral';
this.shadow = el.querySelector('.shadow-container');
this.leftCheek = el.querySelector('.left.cheek');
this.leftCheek = el.querySelector('.right.cheek');
this.leftHand = el.querySelector('.hand-left');
this.rightHand = el.querySelector('.right-hand');
this.activityInterval = null;
}
reset() {
this.scene.remove();
this.scene = this.clone.cloneNode(true);
this.resetRefs();
this.scene.classList.remove('stars-out');
this.scene.style.position = 'absolute';
this.scene.style.left = Math.floor(Math.random() * (document.querySelector('body').getBoundingClientRect().width - 140)) + 'px';
this.scene.style.top = Math.floor(Math.random() * (document.querySelector('body').getBoundingClientRect().height - 160)) + 'px';
this.scene.classList.add('descend');
this.shadow.classList.add('disappear');
document.querySelector('body').append(this.scene);
this.enter();
}
resetRefs() {
this.ghost = this.scene.querySelector('.ghost');
this.face = this.scene.querySelector('.ghost-face');
this.eyes = this.scene.querySelector('.eyes-row');
this.leftEye = this.eyes.querySelector('.left');
this.rightEye = this.eyes.querySelector('.right');
this.mouth = this.scene.querySelector('.mouth');
this.mouthState = 'neutral';
this.isEscaping = false;
this.shadow = this.scene.querySelector('.shadow-container');
this.leftCheek = this.scene.querySelector('.left.cheek');
this.leftCheek = this.scene.querySelector('.right.cheek');
this.leftHand = this.scene.querySelector('.hand-left');
this.rightHand = this.scene.querySelector('.right-hand');
}
blink() {
this.leftEye.classList.add('blink');
this.rightEye.classList.add('blink');
setTimeout(() => {
this.leftEye.classList.remove('blink');
this.rightEye.classList.remove('blink');
}, 50)
}
wave() {
this.leftHand.classList.add('waving');
setTimeout(() => {
this.leftHand.classList.remove('waving');
}, 500);
}
openMouth() {
this.mouth.classList.remove('closed');
this.mouth.classList.add('open');
this.mouthState = 'open';
}
closeMouth() {
this.mouth.classList.remove('open');
this.mouth.classList.add('closed');
this.mouthState = 'closed';
}
neutralMouth() {
this.mouth.classList.remove('open');
this.mouth.classList.remove('closed');
this.mouthState = 'neutral';
}
hover() {
this.ghost.classList.add('hover');
}
surprise() {
this.face.classList.add('surprised');
}
runAway() {
clearInterval(this.activityInterval);
if (!this.isEscaping) {
this.isEscaping = true;
this.scene.classList.add('run-away', 'move-stars-in');
this.scene.classList.remove('stars-out');
setTimeout(() => {
this.shadow.classList.add('disappear');
setTimeout(() => {
this.reset();
}, Math.floor(Math.random()*1000) + 500);
}, 450);
}
}
enter() {
setTimeout(() => {
this.shadow.classList.remove('disappear');
}, 5);
setTimeout(() => {
this.scene.classList.remove('descend');
this.scene.classList.add('stars-out', 'move-stars-out');
}, 600);
setTimeout(() => {
this.hover();
this.prepareEscape();
this.startActivity();
}, 1200)
}
startActivity() {
this.activityInterval = setInterval(() => {
switch (Math.floor(Math.random()*4)) {
case 0:
this.blink();
setTimeout(() => { this.blink() }, 400);
setTimeout(() => { this.blink() }, 1300);
break;
case 1:
this.wave();
break;
default:
break;
}
}, 7000);
}
prepareEscape() {
this.scene.addEventListener('click', () => { this.runAway() }, false);
this.scene.addEventListener('mouseover', () => { this.runAway() }, false);
this.scene.addEventListener('focus', () => { this.runAway() }, false);
}
}
let ghost = new Ghost(document.querySelector('.scene-container'));
ghost.hover();
ghost.startActivity();
ghost.prepareEscape();
html {
height: 100%;
}
body {
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-color: #292B25;
}
.scene-container {
position: relative;
width: 140px;
height: 160px;
}
.scene-container:focus {
outline: none;
}
.scene-container.run-away .ghost {
transform: rotateX(-10deg) scale3d(1.4, 4, 1) translate3d(0, 130%, -30px);
transition: transform 800ms ease;
}
.scene-container.descend .ghost {
transform: translate3d(0, 130%, 0);
}
.ghost-container {
position: relative;
width: 80px;
height: 140px;
padding: 20px 30px 0 30px;
overflow: hidden;
perspective: 30px;
}
.ghost {
position: relative;
height: 115px;
z-index: 1;
transition: transform 2000ms ease-out;
}
.ghost.hover {
-webkit-animation: hover 600ms ease-in-out infinite alternate;
animation: hover 600ms ease-in-out infinite alternate;
}
.ghost-head {
position: relative;
width: 80px;
height: 0;
padding-top: 100%;
border-radius: 100%;
background-color: #F0EFDC;
}
.ghost-head .ghost-face {
position: absolute;
bottom: 10px;
left: 10px;
width: 50px;
height: 30px;
z-index: 1;
}
.eyes-row, .mouth-row {
position: relative;
height: 10px;
}
.mouth-row {
margin-top: 3px;
}
.eye {
height: 10px;
width: 10px;
background-color: #271917;
border-radius: 100%;
position: absolute;
bottom: 0;
transition: height 50ms ease;
}
.eye.left {
left: 5px;
}
.eye.right {
right: 5px;
}
.eye.blink {
height: 0;
}
.mouth {
position: absolute;
left: 50%;
top: 0;
height: 10px;
transform: translate3d(-50%, 0, 0);
}
.mouth .mouth-top {
width: 18px;
height: 2px;
border-radius: 2px 2px 0 0;
background-color: #271917;
}
.mouth .mouth-bottom {
position: absolute;
width: 18px;
height: 8px;
bottom: 0;
overflow: hidden;
transition: height 150ms ease;
}
.mouth .mouth-bottom:after {
content: "";
display: block;
position: absolute;
left: 0;
bottom: 0;
width: 18px;
height: 16px;
border-radius: 100%;
background-color: #271917;
}
.mouth.open .mouth-bottom {
height: 16px;
}
.mouth.closed .mouth-bottom {
height: 0;
}
.cheek {
position: absolute;
top: 0;
width: 12px;
height: 4px;
background-color: #F5C1B6;
border-radius: 100%;
}
.cheek.left {
left: 0;
}
.cheek.right {
right: 0;
}
.ghost-body {
position: absolute;
top: 40px;
height: 0;
width: 80px;
padding-top: 85%;
background-color: #F0EFDC;
}
.ghost-hand {
height: 36px;
width: 22px;
background: #F0EFDC;
border-radius: 100%/90%;
position: absolute;
}
.ghost-hand.hand-left {
left: -12px;
top: 10px;
transform: rotateZ(-45deg);
left: 0;
top: 5px;
transform-origin: bottom center;
}
.ghost-hand.hand-left.waving {
-webkit-animation: waving 400ms linear;
animation: waving 400ms linear;
}
.ghost-hand.hand-right {
right: -12px;
top: 10px;
transform: rotateZ(45deg);
}
.ghost-skirt {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
display: flex;
transform: translateY(50%);
}
.ghost-skirt .pleat {
width: 20%;
height: 8px;
border-radius: 100%;
}
.ghost-skirt .pleat.down {
background-color: #F0EFDC;
}
.ghost-skirt .pleat.up {
background-color: #292B25;
position: relative;
top: 1px;
}
.shadow-container {
transition: transform 800ms ease;
}
.shadow-container.disappear {
transform: scale3d(0, 1, 1);
transition: transform 400ms ease;
}
.shadow {
position: absolute;
top: calc(100% - 4px);
left: 0;
width: 100%;
height: 8px;
background-color: #1B1D18;
border-radius: 100%;
z-index: -1;
}
.shadow-bottom {
position: absolute;
top: 100%;
left: 0;
height: 4px;
width: 100%;
overflow: hidden;
}
.shadow-bottom:after {
content: "";
display: block;
width: 100%;
position: absolute;
height: 8px;
left: 0;
bottom: 0;
border-radius: 100%;
background-color: #1B1D18;
z-index: 2;
}
.star {
position: absolute;
-webkit-animation: twinkle 2s infinite linear;
animation: twinkle 2s infinite linear;
transition: top 400ms ease-out, left 400ms ease-out;
}
.star.round .star-element {
height: 9px;
width: 9px;
border-radius: 100%;
}
.star.pointy {
transform: rotate(-15deg);
}
.star.pointy .star-element {
height: 6px;
width: 6px;
}
.star.pointy .star-element:before, .star.pointy .star-element:after {
content: "";
display: block;
position: absolute;
background: green;
border-radius: 6px;
}
.star.pointy .star-element:before {
height: 6px;
width: 12px;
left: -3px;
top: 0;
transform: skewX(60deg);
}
.star.pointy .star-element:after {
height: 12px;
width: 6px;
right: 0;
bottom: -3px;
transform: skewY(-60deg);
}
.star.orange .star-element, .star.orange .star-element:before, .star.orange .star-element:after {
background-color: #DF814D;
}
.star.yellow .star-element, .star.yellow .star-element:before, .star.yellow .star-element:after {
background-color: #FFD186;
}
.star.blue .star-element, .star.blue .star-element:before, .star.blue .star-element:after {
background-color: #83D0BC;
}
.star-1 {
top: 130%;
left: 40%;
transition-delay: 200ms;
-webkit-animation-delay: 0ms;
animation-delay: 0ms;
z-index: 2;
}
.star-2 {
top: 130%;
left: 44%;
transition-delay: 250ms;
-webkit-animation-delay: 200ms;
animation-delay: 200ms;
}
.star-3 {
top: 130%;
left: 48%;
transition-delay: 300ms;
-webkit-animation-delay: 400ms;
animation-delay: 400ms;
z-index: 2;
}
.star-4 {
top: 130%;
left: 52%;
transition-delay: 350ms;
-webkit-animation-delay: 600ms;
animation-delay: 600ms;
}
.star-5 {
top: 130%;
left: 56%;
transition-delay: 400ms;
-webkit-animation-delay: 800ms;
animation-delay: 800ms;
z-index: 2;
}
.star-6 {
top: 130%;
left: 60%;
transition-delay: 450ms;
-webkit-animation-delay: 1000ms;
animation-delay: 1000ms;
}
.move-stars-out .star-element {
-webkit-animation: star-entrance 1500ms;
animation: star-entrance 1500ms;
}
.stars-out .star {
transition: top 1500ms ease-out, left 1500ms ease-out;
}
.stars-out .star-1 {
top: 75%;
left: 6%;
}
.stars-out .star-2 {
top: 35%;
left: 88%;
}
.stars-out .star-3 {
top: 8%;
left: 20%;
}
.stars-out .star-4 {
top: 70%;
left: 92%;
}
.stars-out .star-5 {
top: 35%;
left: 4%;
}
.stars-out .star-6 {
top: 2%;
left: 70%;
}
.stars-out .star-1 {
transition-delay: 0ms, 0ms;
}
.stars-out .star-1 .star-element {
-webkit-animation-delay: 0ms;
animation-delay: 0ms;
}
.stars-out .star-2 {
transition-delay: 200ms, 200ms;
}
.stars-out .star-2 .star-element {
-webkit-animation-delay: 200ms;
animation-delay: 200ms;
}
.stars-out .star-3 {
transition-delay: 400ms, 400ms;
}
.stars-out .star-3 .star-element {
-webkit-animation-delay: 400ms;
animation-delay: 400ms;
}
.stars-out .star-4 {
transition-delay: 600ms, 600ms;
}
.stars-out .star-4 .star-element {
-webkit-animation-delay: 600ms;
animation-delay: 600ms;
}
.stars-out .star-5 {
transition-delay: 800ms, 800ms;
}
.stars-out .star-5 .star-element {
-webkit-animation-delay: 800ms;
animation-delay: 800ms;
}
.stars-out .star-6 {
transition-delay: 1000ms, 1000ms;
}
.stars-out .star-6 .star-element {
-webkit-animation-delay: 1000ms;
animation-delay: 1000ms;
}
.move-stars-in .star-element {
-webkit-animation: star-exit 400ms linear;
animation: star-exit 400ms linear;
}
.move-stars-in .star-1 .star-element {
-webkit-animation-delay: 100ms;
animation-delay: 100ms;
}
.move-stars-in .star-2 .star-element {
-webkit-animation-delay: 150ms;
animation-delay: 150ms;
}
.move-stars-in .star-3 .star-element {
-webkit-animation-delay: 200ms;
animation-delay: 200ms;
}
.move-stars-in .star-4 .star-element {
-webkit-animation-delay: 250ms;
animation-delay: 250ms;
}
.move-stars-in .star-5 .star-element {
-webkit-animation-delay: 300ms;
animation-delay: 300ms;
}
.move-stars-in .star-6 .star-element {
-webkit-animation-delay: 350ms;
animation-delay: 350ms;
}
@-webkit-keyframes hover {
0% {
top: 0;
}
100% {
top: 8px;
}
}
@keyframes hover {
0% {
top: 0;
}
100% {
top: 8px;
}
}
@-webkit-keyframes star-entrance {
0% {
transform: rotate(-735deg) scale(0, 0);
}
100% {
transform: rotate(0) scale(1, 1);
}
}
@keyframes star-entrance {
0% {
transform: rotate(-735deg) scale(0, 0);
}
100% {
transform: rotate(0) scale(1, 1);
}
}
@-webkit-keyframes star-exit {
0% {
transform: rotate(0) scale(1, 1);
}
100% {
transform: rotate(360deg) scale(0, 0);
}
}
@keyframes star-exit {
0% {
transform: rotate(0) scale(1, 1);
}
100% {
transform: rotate(360deg) scale(0, 0);
}
}
@-webkit-keyframes twinkle {
0% {
transform: rotate(0deg) scale(1, 1);
}
25% {
transform: rotate(10deg) scale(0.8, 0.8);
}
50% {
transform: rotate(0deg) scale(0.9, 0.9);
}
75% {
transform: rotate(-20deg) scale(0.6, 0.6);
}
100% {
transform: rotate(0deg) scale(1, 1);
}
}
@keyframes twinkle {
0% {
transform: rotate(0deg) scale(1, 1);
}
25% {
transform: rotate(10deg) scale(0.8, 0.8);
}
50% {
transform: rotate(0deg) scale(0.9, 0.9);
}
75% {
transform: rotate(-20deg) scale(0.6, 0.6);
}
100% {
transform: rotate(0deg) scale(1, 1);
}
}
@-webkit-keyframes waving {
0% {
transform: rotate(-45deg);
}
25% {
transform: rotate(-55deg);
}
50% {
transform: rotate(-45deg);
}
75% {
transform: rotate(-55deg);
}
100% {
transform: rotate(-45deg);
}
}
@keyframes waving {
0% {
transform: rotate(-45deg);
}
25% {
transform: rotate(-55deg);
}
50% {
transform: rotate(-45deg);
}
75% {
transform: rotate(-55deg);
}
100% {
transform: rotate(-45deg);
}
}