<! 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) ; }
}