<div class="game">
<!-- /////////// -->
<!-- Fishing rod -->
<!-- /////////// -->
<div class="fishing-rod">
<img class="fill-container" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-fishing-rod.svg" alt="">
<canvas class="reel-line-tension" data-element="reel-line-tension"></canvas>
<div class="reel-handle">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-reel-handle.svg" alt="">
</div>
<div class="reel-line"></div>
</div>
<!-- ///////// -->
<!-- Game body -->
<!-- ///////// -->
<div class="game-body">
<img class="fill-container" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-game-body-bg.svg" alt="">
<canvas class="bubbles" data-element="bubbles"></canvas>
<canvas class="seaweed" data-element="seaweed"></canvas>
<img class="seafloor fill-container" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-sea-floor.svg" alt="">
<div class="fish">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-fish.svg" alt="">
</div>
<div class="indicator">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-indicator.svg" alt="">
</div>
<img class="glass fill-container" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-glass.svg" alt="">
</div>
<!-- //////////// -->
<!-- Progress bar -->
<!-- //////////// -->
<div class="progress-bar">
<img class="fill-container" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-progress-bar-bg.svg" alt="">
<div class="progress-gradient-wrapper">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-progress-gradient.svg" class="progress-gradient" alt="">
</div>
</div>
</div>
<!-- .game -->
<!-- /////// -->
<!-- Success -->
<!-- /////// -->
<div class="success">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-nice-catch.png" alt="" class="nice-catch">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-perfect.png" alt="" class="perfect">
</div>
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: radial-gradient(ellipse at center, #88c4c2 0%,#56838f 80%);
display: flex;
align-items: center;
justify-content: center;
}
.game {
display: flex;
align-items: center;
opacity: 1;
}
.fill-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* ----------- */
/* Fishing rod */
/* ----------- */
.fishing-rod {
width: 46px;
height: 352px;
position: relative;
margin-right: 10px;
}
.reel-handle {
width: 14px;
height: 14px;
position: absolute;
top: 81.2%;
left: 1.8%;
z-index: 10;
}
.reel-handle img {
animation-name: rotate;
animation-iteration-count: infinite;
animation-duration: 0.35s;
animation-timing-function: linear;
transform-origin: 12px 2.5px;
animation-play-state: paused;
}
@keyframes rotate {
0% {transform: rotate(0deg)}
100% {transform: rotate(360deg)}
}
.indicator-active .reel-handle img {
animation-play-state: running;
}
.reel-line {
width: 18px;
height: 16px;
position: absolute;
top: 265px;
left: 4px;
background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/49240/game-reel-loop.png') top left repeat-x;
background-size: auto 100%;
z-index: 5;
animation-name: reel;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-play-state: paused;
opacity: 0.3;
}
.indicator-active .reel-line {
animation-play-state: running;
}
@keyframes reel {
0% {background-position: 0;}
100% {background-position: 139px}
}
.reel-line-tension {
width: 42%;
height: 13.5%;
position: absolute;
bottom: 26.8%;
left: 29%;
/* background: red;
opacity: 0.5; */
}
/* --------- */
/* Game body */
/* --------- */
.game-body {
width: 36px;
height: 350px;
position: relative;
}
.seafloor {
position: relative;
z-index: 5;
}
.seaweed {
width: 94.5%;
height: 19%;
position: absolute;
bottom: 4%;
left: 3%;
z-index: 10;
}
.bubbles {
width: 94.5%;
height: 95.2%;
position: absolute;
bottom: 4%;
left: 2.7%;
/* background: red;
opacity: 0.5; */
}
.fish {
width: 25px;
height: 17px;
position: absolute;
top: calc(100% - 17px);
left: 18%;
opacity: 0.8;
will-change: top;
}
.indicator {
width: 34px;
height: 46px;
position: absolute;
top: calc(100% - 46px);
left: 3%;
opacity: 0.8;
z-index: 30;
}
.glass {
z-index: 20;
}
/* ------------ */
/* Progress bar */
/* ------------ */
.progress-bar {
width: 16px;
height: 334px;
position: relative;
overflow: hidden;
}
.progress-gradient-wrapper {
position: absolute;
bottom: 1px;
left: 0;
width: 15px;
height: 100%;
overflow: hidden;
border-top-right-radius: 4px;
will-change: height;
}
.progress-gradient {
position: absolute;
bottom: 0;
left: 0;
width: 15px;
height: 332px;
display: block;
}
/* ------- */
/* Success */
/* ------- */
.success {
width: 200px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
flex-direction: column;
z-index: 100;
opacity: 0;
display: none;
cursor: pointer;
}
.nice-catch {
width: 100%;
position: relative;
z-index: 20;
}
.perfect {
width: 46.743295%;
margin-top: -4px;
transform-origin: top;
position: relative;
z-index: 10;
}
// ----
// Game
// ----
(function() {
let gameOver = false;
// --------------
// Animation loop
// --------------
function animationLoop() {
if (!gameOver) {
indicator.updatePosition();
indicator.detectCollision();
progressBar.updateUi();
progressBar.detectGameEnd();
fish.updateFishPosition();
requestAnimationFrame(animationLoop);
}
}
// ---------
// Indicator
// ---------
class Indicator {
constructor() {
this.indicator = document.querySelector('.indicator');
this.height = this.indicator.clientHeight;
this.y = 0;
this.velocity = 0;
this.acceleration = 0;
this.topBounds = (gameBody.clientHeight * -1) + 48;
this.bottomBounds = 0;
}
applyForce(force) {
this.acceleration += force;
}
updatePosition() {
this.velocity += this.acceleration;
this.y += this.velocity;
// Reset acceleration
indicator.acceleration = 0;
// Change direction when hitting the bottom + add friction
if (this.y > this.bottomBounds) {
this.y = 0;
this.velocity *= 0.5;
this.velocity *= -1;
}
// Prevent from going beyond the top
// Don't apply button forces when beyond the top
// console.log(indicator.y, this.topBounds);
if (indicator.y < this.topBounds) {
indicator.y = this.topBounds;
indicator.velocity = 0;
} else {
if (keyPressed) {
indicator.applyForce(-0.5);
}
}
// Apply constant force
indicator.applyForce(0.3);
// Update object position
this.indicator.style.transform = `translateY(${this.y}px)`;
}
detectCollision() {
if (
fish.y < this.y && fish.y > this.y - this.height ||
fish.y - fish.height < this.y && fish.y - fish.height > this.y - this.height
) {
progressBar.fill();
document.body.classList.add('collision');
} else {
progressBar.drain();
document.body.classList.remove('collision');
}
}
}
// ----
// Fish
// ----
class Fish {
constructor() {
this.fish = document.querySelector('.fish');
this.height = this.fish.clientHeight;
this.y = 5;
this.direction = null;
this.randomPosition = null;
this.randomCountdown = null;
this.speed = 2;
}
resetPosition() {
this.y = 5;
}
updateFishPosition() {
if (!this.randomPosition || this.randomCountdown < 0) {
this.randomPosition = Math.ceil(Math.random() * (gameBody.clientHeight - this.height)) * -1;
this.randomCountdown = Math.abs(this.y - this.randomPosition);
this.speed = Math.abs(Math.random() * (3 - 1) + 1);
};
if (this.randomPosition < this.y) {
this.y -= this.speed;
} else {
this.y += this.speed;
}
this.fish.style.transform = `translateY(${this.y}px)`;
this.randomCountdown -= this.speed;
}
}
// ------------
// Progress bar
// ------------
class ProgressBar {
constructor() {
this.wrapper = document.querySelector('.progress-bar');
this.progressBar = this.wrapper.querySelector('.progress-gradient-wrapper');
this.progress = 50;
}
reset() {
this.progress = 50;
}
drain() {
if (this.progress > 0) this.progress -= 0.4;
if (this.progress < 1) this.progress = 0;
}
fill() {
if (this.progress < 100) this.progress += 0.3;
}
detectGameEnd() {
if (this.progress >= 100) {
// successTimeline().play();
successTimeline().invalidate().play(0);
gameOver = true;
}
}
updateUi() {
this.progressBar.style.height = `${this.progress}%`
}
}
// -----------
// Application
// -----------
const gameBody = document.querySelector('.game-body');
let keyPressed = false;
const indicator = new Indicator();
const progressBar = new ProgressBar();
const fish = new Fish();
// ------------
// Mouse events
// ------------
window.addEventListener('mousedown', indicatorActive);
window.addEventListener('mouseup', indicatorInactive);
window.addEventListener('keydown', indicatorActive);
window.addEventListener('keyup', indicatorInactive);
window.addEventListener('touchstart', indicatorActive);
window.addEventListener('touchend', indicatorInactive);
function indicatorActive () {
if (!keyPressed) {
keyPressed = true;
document.body.classList.add('indicator-active');
}
}
function indicatorInactive () {
if (keyPressed) {
keyPressed = false;
document.body.classList.remove('indicator-active');
}
}
// ----------
// Reset game
// ----------
const niceCatch = document.querySelector('.nice-catch');
const perfect = document.querySelector('.perfect');
const successButton = document.querySelector('.success');
const game = document.querySelector('.game');
successButton.addEventListener('click', resetGame);
function resetGame() {
progressBar.reset();
fish.resetPosition();
successButton.removeAttribute('style');
niceCatch.removeAttribute('style');
perfect.removeAttribute('style');
game.removeAttribute('style');
gameOver = false;
animationLoop();
}
// ----------------
// Success timeline
// ----------------
function successTimeline() {
TweenMax.set(".success", {display: 'flex'});
TweenMax.set(".nice-catch", {y: 50});
TweenMax.set(".perfect", {perspective:800});
TweenMax.set(".perfect", {transformStyle:"preserve-3d"});
TweenMax.set(".perfect", {rotationX:-90});
const tl = new TimelineMax({paused: true})
.to('.game', 0.2, {opacity: 0})
.to('.success', 0.5, {ease: Power3.easeOut, opacity: 1}, 'ending')
.to('.nice-catch', 0.5, {ease: Power3.easeOut, y: 0}, 'ending')
.to('.perfect', 3, {ease: Elastic.easeOut.config(1, 0.3), rotationX: 0}, '+=0.2');
return tl;
}
// -------------
// Initiate loop
// -------------
animationLoop();
})();
// -------
// Seaweed
// -------
(function() {
let seaweed = [];
const canvas = document.querySelector('[data-element="seaweed"]');
canvas.width = canvas.clientWidth * 2;
canvas.height = canvas.clientHeight * 2;
const context = canvas.getContext('2d');
function animationLoop() {
clearCanvas();
seaweed.forEach(seaweed => seaweed.draw());
requestAnimationFrame(animationLoop);
}
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
class Seaweed {
constructor(segments, spread, xoff) {
this.segments = segments;
this.segmentSpread = spread;
this.x = 0;
this.xoff = xoff;
this.y = 0;
this.radius = 1;
this.sin = Math.random() * 10;
}
draw() {
context.beginPath();
context.strokeStyle="#143e5a";
context.fillStyle="#143e5a";
context.lineWidth=2;
for (let i = this.segments; i >= 0; i--) {
if (i === this.segments) {
context.moveTo(
Math.sin(this.sin + i) * i/2.5 + this.xoff,
canvas.height + (-i * this.segmentSpread),
);
} else {
context.lineTo(
Math.sin(this.sin + i) * i/2.5 + this.xoff,
canvas.height + (-i * this.segmentSpread),
);
}
// context.arc(Math.sin(this.sin + i) * 10 + 30, this.y + (this.segmentSpread * i), this.radius, 0, 2*Math.PI);
}
context.stroke();
this.sin += 0.05;
}
}
seaweed.push(new Seaweed(6, 8, 25));
seaweed.push(new Seaweed(8, 10, 35));
seaweed.push(new Seaweed(4, 8, 45));
animationLoop()
})();
// -----------------
// Reel line tension
// -----------------
(function() {
let line = null;
const canvas = document.querySelector('[data-element="reel-line-tension"]');
canvas.width = canvas.clientWidth * 2;
canvas.height = canvas.clientHeight * 2;
const context = canvas.getContext('2d');
function animationLoop() {
clearCanvas();
line.draw();
line.animate();
requestAnimationFrame(animationLoop);
}
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
class Line {
constructor() {
this.tension = 0;
this.tensionDirection = 'right';
}
draw() {
context.beginPath();
context.strokeStyle="#18343d";
context.lineWidth=1.3;
context.moveTo(canvas.width, 0);
context.bezierCurveTo(
canvas.width,canvas.height/2 + this.tension,
canvas.width/2,canvas.height + this.tension,
0,canvas.height
);
context.stroke();
}
animate() {
if (document.body.classList.contains('collision')) {
if (this.tension > -30) this.tension -= 8;
} else {
if (this.tension < 0) this.tension += 4;
}
}
}
line = new Line();
animationLoop()
})();
// -------
// Bubbles
// -------
(function() {
let bubbles = {};
let bubblesCreated = 0;
const canvas = document.querySelector('[data-element="bubbles"]');
canvas.width = canvas.clientWidth * 2;
canvas.height = canvas.clientHeight * 2;
const context = canvas.getContext('2d');
function animationLoop() {
clearCanvas();
Object.keys(bubbles).forEach(bubble => bubbles[bubble].draw());
requestAnimationFrame(animationLoop);
}
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
class Bubble {
constructor() {
this.index = Object.keys(bubbles).length;
this.radius = Math.random() * (6 - 2) + 2;
this.y = canvas.height + this.radius;
this.x = canvas.width * Math.random() - this.radius;
this.sin = (this.style > 0.5) ? 0 : 5;
this.style = Math.random();
this.childAdded = false;
this.speed = 1;
this.sway = Math.random() * (0.03 - 0.01) + 0.01;
this.swayDistance = Math.random() * (canvas.width - canvas.width/2) + canvas.width/2;
}
draw() {
context.beginPath();
context.strokeStyle="#abe2f9";
context.lineWidth=2;
context.arc(this.x + this.radius, this.y + this.radius, this.radius, 0, 2*Math.PI);
context.stroke();
this.x = (Math.sin(this.sin) * this.swayDistance) + this.swayDistance - this.radius;
this.sin += this.sway;
this.y -= this.speed;
if (this.y + this.radius < 0) {
delete bubbles[this.index];
}
if (this.y < canvas.height * 0.6) {
if (!this.childAdded) {
bubbles[bubblesCreated] = new Bubble();
bubblesCreated++;
this.childAdded = true;
}
}
}
}
bubbles[bubblesCreated] = new Bubble();
bubblesCreated++;
animationLoop()
})();
View Compiled
This Pen doesn't use any external CSS resources.