<div class="fish-wrapper">
<div class="fish">
<div class="fish__skeleton"></div>
<div class="fish__inner">
<!--body-->
<div class="fish__body"></div>
<div class="fish__body"></div>
<div class="fish__body"></div>
<div class="fish__body"></div>
<!--head-->
<div class="fish__head"></div>
<div class="fish__head fish__head--2"></div>
<div class="fish__head fish__head--3"></div>
<div class="fish__head fish__head--4"></div>
<div class="fish__tail-main"></div>
<div class="fish__tail-fork"></div>
<div class="fish__fin"></div>
<div class="fish__fin fish__fin--2"></div>
</div>
</div>
</div>
<div class="bubbles">
<div class="bubbles__inner">
<div class="bubbles__bubble"></div>
<div class="bubbles__bubble"></div>
<div class="bubbles__bubble"></div>
</div>
</div>
<div class="content">
</div>
@import url('https://fonts.googleapis.com/css2?family=Judson&display=swap');
* {
box-sizing: border-box;
}
body {
font-family: 'Judson', serif;
background: linear-gradient(to bottom,
rgba(99, 167, 191, 1),
rgba(94, 86, 179, 1),
);
max-width: 100vw;
min-height: 100vh;
overflow-x: hidden;
color: white;
position: relative;
margin: 0;
&::after {
position: fixed;
content: '';
pointer-events: none;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: radial-gradient(circle at center, transparent, rgba(0, 0, 0, 0.5));
}
@media (min-width: 40em) {
font-size: 2rem;
}
}
.rays {
--r: 10deg;
--c: rgba(255, 251, 227, 0.2);
--size: max(60vh, 80rem);
--mask: radial-gradient(circle at center, black, transparent 50%);
position: fixed;
pointer-events: none;
top: calc(var(--size) * -0.55);
left: 50%;
width: var(--size);
height: var(--size);
pointer-events: none;
> div {
width: 100%;
height: 100%;
border-radius: 50%;
background: repeating-conic-gradient(var(--c), var(--c) var(--r), transparent var(--r), transparent calc(var(--r) * 2));
// -webkit-mask-image: var(--mask);
// mask-image: var(--mask);
// animation: raysRotate 120000ms linear infinite;
}
}
@keyframes raysRotate {
50% {
transform: rotate(180deg) scale(1.5);
}
100% {
transform: rotate(360deg) scale(1);
}
}
.fish-wrapper {
--mask: linear-gradient(180deg, rgba(0, 0, 0, 1.0), transparent);
width: 100%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
perspective: 100rem;
perspective-origin: center center;
transform-style: preserve-3d;
pointer-events: none;
-webkit-mask-image: var(--mask);
mask-image: var(--mask);
z-index: 2;
}
.fish {
--bodyW: 4rem;
--o: 0.95;
--l: 100%;
--c: hsla(250deg, 50%, var(--l), var(--o, 0.6));
position: relative;
width: 20rem;
height: 20rem;
transform-style: preserve-3d;
transform-origin: center;
transform: translate3d(10rem, 5rem, 0) rotateX(20deg) rotateY(0deg);
}
.fish__skeleton {
--clip: polygon(0 4rem, 45% 0, 55% 0, 100% 4rem, 50% 100%);
position: absolute;
width: 100%;
height: 100%;
background: repeating-linear-gradient(0deg, var(--c), var(--c) 0.1rem, transparent 0, transparent 0.5rem), linear-gradient(var(--c) 4rem, transparent 4rem), linear-gradient(90deg, transparent 1.9rem, var(--c) 0, var(--c) 2.1rem, transparent 0);
top: 1rem;
left: 3rem;
width: var(--bodyW);
height: 16rem;
-webkit-clip-path: var(--clip);
clip-path: var(--clip);
opacity: 0;
transform: translate3d(0, 0, -2rem) rotate(90deg);
transform-origin: center center;
}
.fish__inner {
--a: 9.5deg;
width: 6rem;
height: 20rem;
transform-style: preserve-3d;
transform: rotate(90deg);
}
.fish__body {
--l: 75%;
--c: hsla(250deg, 50%, var(--l), var(--o, 0.6));
position: absolute;
top: 4rem;
left: 0;
width: var(--bodyW);
height: 12rem;
background: var(--c);
clip-path: polygon(0 0, 100% 0, 50% 100%);
transform: translateZ(-2rem) rotateX(var(--a));
transform-origin: center top;
&:nth-child(2) {
--i: 2;
--l: 75%;
transform: translateZ(2rem) rotateX(calc(var(--a) * -1));
}
&:nth-child(3) {
--i: 3;
--l: 95%;
transform: rotateY(90deg) translate3d(-2rem, 0, 0) rotateX(var(--a));
transform-origin: left top;
}
&:nth-child(4) {
--i: 4;
--l: 50%;
transform: rotateY(90deg) translate3d(2rem, 0, 0) rotateX(calc(var(--a) * -1));
transform-origin: right top;
}
}
.fish__head {
--a: 23.5deg;
--l: 85%;
--c: hsla(250deg, 50%, var(--l), var(--o, 0.6));
position: absolute;
top: 0;
left: 0;
width: var(--bodyW);
height: 4rem;
background: var(--c);
clip-path: polygon(40% 0, 60% 0, 100% 100%, 0 100%);
transform: translateZ(2rem) rotateX(var(--a));
transform-origin: center bottom;
&--2 {
--i: 2;
--l: 80%;
transform: translateZ(-2rem) rotateX(calc(var(--a) * -1));
}
&--3 {
--i: 3;
--l: 90%;
transform: rotateY(90deg) translate3d(-2rem, 0, 0) rotateX(calc(var(--a) * -1));
transform-origin: left bottom;
}
&--4 {
--l: 55%;
transform: rotateY(90deg) translate3d(2rem, 0, 0) rotateX(var(--a));
transform-origin: right bottom;
}
}
.fish__tail-main {
--o: 0.9;
--l: 90%;
--c: hsla(250deg, 50%, var(--l), var(--o, 0.6));
width: 4rem;
height: 4rem;
background-color: var(--c);
position: absolute;
left: 0;
bottom: 4rem;
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.fish__tail-fork {
--o: 0.9;
--l: 95%;
--c: hsla(250deg, 50%, var(--l), var(--o, 0.6));
width: 4rem;
height: 4rem;
background-color: var(--c);
position: absolute;
left: 0;
bottom: 0;
clip-path: polygon(0 0, 100% 0, 100% 70%, 90% 100%, 70% 70%, 50% 30%, 30% 70%, 10% 100%, 0 70%);
transform-origin: top center;
transform: rotateX(-45deg);
animation: tail 1000ms infinite alternate;
}
.fish__fin {
width: 1.5rem;
height: 4rem;
background-color: var(--c);
position: absolute;
top: 6rem;
left: 1.5rem;
clip-path: polygon(50% 0, 100% 30%, 100% 60%, 50% 100%, 0 60%, 0 30%);
transform-origin: top center;
transform: translateZ(2rem) rotateY(0deg) rotateX(5deg) rotate(10deg);
animation: fin 1500ms infinite alternate linear;
&--2 {
transform: translateZ(-2rem) rotateY(0deg) rotateX(-5deg) rotate(10deg);
animation: fin2 1500ms infinite alternate linear;
}
}
@keyframes tail {
to {
transform: rotateX(45deg);
}
}
@keyframes fin {
100% {
transform: translateZ(2rem) rotateY(10deg) rotateX(20deg) rotate(-10deg);
}
}
@keyframes fin2 {
100% {
transform: translateZ(-2rem) rotateY(-10deg) rotateX(-20deg) rotate(-10deg);
}
}
/* Lights */
.lights {
position: fixed;
pointer-events: none;
top: 0;
left: 0;
width: 100%;
height: 100vh;
}
.lights__group {
position: relative;
height: 100%;
}
.lights__light {
--size: 0.35rem;
width: var(--size);
height: var(--size);
position: absolute;
background: rgba(255, 255, 255, 1);
border-radius: 100%;
top: 10%;
left: 25%;
filter: blur(0.1rem);
animation: blink 2500ms var(--d, 0ms) infinite alternate;
&:nth-child(2) {
--d: 200ms;
top: 40%;
left: 12%;
}
&:nth-child(3) {
--d: 350ms;
top: 60%;
left: 18%;
}
&:nth-child(4) {
--d: 600ms;
top: 25%;
left: 66%;
}
&:nth-child(5) {
--d: 1210ms;
top: 43%;
left: 55%;
}
&:nth-child(6) {
--d: 420ms;
top: 90%;
left: 37%;
}
&:nth-child(7) {
--d: 1100ms;
top: 82%;
left: 91%;
}
&:nth-child(8) {
--d: 1560ms;
top: 67%;
left: 81%;
}
}
@keyframes blink {
to {
opacity: 0;
}
}
.content {
position: relative;
z-index: 1;
padding-bottom: 100vh;
}
section {
height: 100vh;
width: 100%;
margin-top: 100vh;
&:nth-child(4n),
&:nth-child(4n - 1) {
--col: 3;
}
}
.section__content {
width: 100%;
position: fixed;
top: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
@media (min-width: 50rem) {
display: grid;
grid-template-columns: repeat(3, minmax(0, 25rem));
gap: 2rem;
padding: 3rem;
> * {
grid-column: var(--col, 1);
opacity: 0;
}
}
}
.bubbles {
position: fixed;
top: 0;
left: 5rem;
// transform-style: preserve-3d;
transform-origin: center;
transform: translate3d(10rem, 5rem, 0) rotateX(20deg) rotateY(0deg)
}
.bubbles__inner {
width: 10rem;
height: 10rem;
}
.bubbles__bubble {
--c: rgba(255, 255, 255, 0.4);
--size: 2.5rem;
position: absolute;
width: var(--size);
height: var(--size);
border-radius: 50%;
background: radial-gradient(transparent 30%, var(--c, white)), radial-gradient(circle at 100% 0%, transparent 30%, var(--c, white));
transform-origin: center;
transform: scale(0);
opacity: 0;
&:nth-child(2) {
--size: 1.8rem;
top: 3rem;
left: 2rem;
}
&:nth-child(3) {
--size: 1.2rem;
top: 6rem;
left: 0;
}
}
.indicator {
text-align: center;
position: fixed;
bottom: 1rem;
left: 50%;
transform: translate3d(-50%, 0, 0);
font-size: 1.2rem;
span {
display: block;
&:nth-child(2) {
animation: arrowMove 600ms infinite alternate;
}
}
}
@keyframes arrowMove {
to {
transform: translate3d(0, 0.5rem, 0);
}
}
View Compiled
gsap.registerPlugin(ScrollTrigger)
gsap.registerPlugin(MotionPathPlugin)
const sections = [...document.querySelectorAll('section')]
const fish = document.querySelector('.fish')
const fishHeadAndBody =
[
...document.querySelectorAll('.fish__head'),
...document.querySelectorAll('.fish__body')
]
const lights = [...document.querySelectorAll('[data-lights]')]
const rays = document.querySelector('[data-rays]')
const path = [
// 1
{ x: 800, y: 200 },
{ x: 900, y: 20 },
{ x: 1100, y: 100 },
// 2
{ x: 1000, y: 200 },
{ x: 900, y: 20 },
{ x: 10, y: 500 },
// 3
{ x: 100, y: 300 },
{ x: 500, y: 400 },
{ x: 1000, y: 200 },
// 4
{ x: 1100, y: 300 },
{ x: 400, y: 400 },
{ x: 200, y: 250 },
// 5
{ x: 100, y: 300 },
{ x: 500, y: 450 },
{ x: 1100, y: 500 }
]
const bubbles = gsap.timeline()
bubbles.set('.bubbles__bubble', {
y: 100,
})
bubbles.to('.bubbles__bubble', {
scale: 1.2,
y: -300,
opacity: 1,
duration: 2,
stagger: 0.2,
})
bubbles.to('.bubbles__bubble', {
scale: 1,
opacity: 0,
duration: 1,
}, '-=1')
bubbles.pause()
const tl = gsap.timeline({repeat:-1})
tl.to(fish, {
motionPath: {
path: path,
align: 'self',
alignOrigin: [0.5, 0.5],
autoRotate: true
},
duration: 10,
immediateRender: true,
// ease: 'none'
})
tl.to('.indicator', {
opacity: 0
}, 0)
tl.to(fish, {
rotateX: 180
}, 1)
tl.to(fish, {
rotateX: 0
}, 2.5)
tl.to(fish, {
z: -500,
duration: 2,
}, 2.5)
tl.to(fish, {
rotateX: 180
}, 4)
tl.to(fish, {
rotateX: 0
}, 5.5)
tl.to(fish, {
z: -50,
duration: 2,
}, 5)
tl.to(fish, {
rotate: 0,
duration: 1,
}, '-=1')
tl.to('.fish__skeleton', {
opacity: 0.6,
duration: 0.1,
repeat: 4
}, '-=3')
tl.to(fishHeadAndBody, {
opacity: 0,
duration: 0.1,
repeat: 4
}, '-=3')
tl.to('.fish__inner', {
opacity: 0.1,
duration: 1
}, '-=1')
tl.to('.fish__skeleton', {
opacity: 0.1,
duration: 1
}, '-=1')
bubbles.play()
// tl.pause()
const rotateFish = (self) => {
if (self.direction === -1) {
gsap.to(fish, { rotationY: 180, duration: 0.4 })
} else {
gsap.to(fish, { rotationY: 0, duration: 0.4 })
}
}
This Pen doesn't use any external CSS resources.