<div id="intro">
<div id="pass-intro">Try it !</div>
</div>
<div id="wrap-button">
<div id="wrap-message">
<div id="message-1">Hover me</div>
<div id="message-2">Move me</div>
</div>
</div>
<div id="lighted-zone"></div>
/* Start */
html {
margin : 0;
padding : 0;
}
body {
margin : 0;
padding : 0;
width : 100%;
height: 100vh;
display : flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color : #000000;
overflow : hidden;
}
/* Intro */
#intro {
position : absolute;
height: 100vh;
width: 100vw;
background-image: url('https://johandavid.fr/images/intro_shadows.gif') ;
background-size : cover;
background-position: 50% 50%;
z-index : 999;
display : flex;
justify-content: center;
align-items: center;
transition : transform 800ms;
}
.intro-leaves {
transform : translateY(-100%);
}
#pass-intro {
transition : transform 200ms;
border : 2px solid white;
}
#pass-intro:hover {
background-color: white;
color: #212121;
box-shadow : -8px 8px 0px #212121;
filter : blur(0px);
}
/* Light Button */
#wrap-button, #pass-intro {
position : absolute;
width: 167px;
height: 50px;
background-color: #212121;
color: white;
text-align: center;
font-family: 'League Gothic', sans-serif;
font-size: 2em;
cursor: pointer;
line-height: 1.5;
letter-spacing: 2px;
user-select : none;
}
#wrap-button:before, #wrap-button:after {
position : absolute;
content : '';
background: linear-gradient(90deg, #1e2529, #fff7e9);
background-size : 800% 100%;
top : -3px;
bottom : -3px;
left: -3px;
right: -3px;
z-index: -1;
}
#wrap-button:after {
filter : blur(8px);
}
.button-on:before, .button-on:after {
animation : lightRotating 400ms forwards;
}
#wrap-message {
position : absolute;
top : 0;
left: 0;
right: 0;
bottom: 0;
overflow : hidden;
}
#message-1, #message-2 {
width: 167px;
height: 50px;
transition : transform 200ms;
}
.up {
transform : translateY(-50px);
}
/* Back light */
#lighted-zone {
position : absolute;
width : 200vw;
height: 200vh;
top: -50%;
left : -50%;
background: radial-gradient(closest-side, #484848, #2f2f2f, #181818, #0e0e0e, black);
z-index : -2;
opacity : 0;
}
.lighted-zone-on {
animation : zone-on 600ms forwards;
}
/* Circles */
.circle-body {
position : absolute;
height : 40px;
width: 40px;
border-radius: 40px;
background-color: #646464;
z-index : 1;
}
.circle {
position : absolute;
height : 40px;
width: 40px;
filter: brightness(0%);
opacity : 0;
}
.circle-on {
animation : zone-on 600ms forwards;
}
/* Shadows */
.wrap-shadow {
position : absolute;
top: 20px;
width: 40px;
height : 600px;
transform-origin: top;
transform : perspective(600px) rotateX(40deg);
}
.shadow {
height : 100%;
width : 100%;
background : linear-gradient( rgba(0, 0, 0, 0.3) 10% , transparent 100% );
position : absolute;
}
.shadow-blur {
height : 100%;
width : 100%;
background : linear-gradient( transparent 10%, rgba(0, 0, 0, 0.8) 70%, transparent 90% );
filter : blur(4px);
position : absolute;
}
/* Panel (not implemented yet) */
#panel {
position : absolute;
top : 15px;
left: 15px;
z-index: 999;
}
#message-panel {
font-family: 'League Gothic', sans-serif;
font-size : 1.3em;
color : white;
}
#wrap-colored-dots {
display: flex;
justify-content: space-between;
height: 40px;
background-color: #606060;
flex-direction: row;
align-items: center;
}
.colored-dot {
height: 14px;
width: 14px;
border-radius: 10px;
border: 1px solid #262626;
cursor: pointer;
margin: 0px 10px 0px 10px;
}
.colored-dot:hover {
border : 1px solid white;
}
.red-dot { background-color : red; }
.green-dot { background-color : green; }
.blue-dot { background-color : blue; }
.white-dot { background-color : white; }
/* Animations */
@keyframes lightRotating {
0% {
background-position : 0%;
}
100% {
background-position : 100%;
}
}
@keyframes zone-on {
0% {
opacity : 0;
}
100% {
opacity : 1;
}
}
/* Future improvements
#panel {
position : absolute;
top : 15px;
left: 15px;
z-index: 999;
}
#message-panel {
font-family: 'League Gothic', sans-serif;
font-size : 1.3em;
color : white;
}
#wrap-colored-dots {
display: flex;
justify-content: space-between;
height: 40px;
background-color: #606060;
flex-direction: row;
align-items: center;
}
.colored-dot {
height: 14px;
width: 14px;
border-radius: 10px;
border: 1px solid #262626;
cursor: pointer;
margin: 0px 10px 0px 10px;
}
.colored-dot:hover {
border : 1px solid white;
}
.red-dot { background-color : red; }
.green-dot { background-color : green; }
.blue-dot { background-color : blue; }
.white-dot { background-color : white; } */
class button {
lightMode = false;
moveMode = false;
posX = null;
posY = null;
catchPointX = null;
catchPointY = null;
circlesPos = [];
circlesSizes = [];
dists = [];
static HOWMANYCIRCLES = 13;
constructor () {
this.target = document.getElementById('wrap-button');
this.initTargetPos();
this.light = document.querySelector('#lighted-zone');
this.message = document.getElementById('message-1');
this.message2 = document.getElementById('message-2');
this.events();
this.circles();
window.onresize = this.resize.bind(this);
}
initTargetPos() {
this.targetPos = this.target.getBoundingClientRect();
}
resize() {
if (!this.target.style.top) {
this.initTargetPos();
}
this.moveLight();
}
events() {
this.target.addEventListener('mouseover', (e)=>{
this.over(e);
});
this.target.addEventListener('mouseout', (e)=>{
this.out(e);
});
this.target.addEventListener('mousedown', (e)=>{
this.clic(e);
});
this.target.addEventListener('mouseup', (e)=>{
this.endclic(e);
});
document.addEventListener('mousemove', (e)=>{
this.mousemove(e);
});
document.addEventListener('mouseout', (e)=>{
this.mouseOutWindow(e);
});
}
create(className) {
let element = document.createElement('div');
element.classList.add(className);
return element;
}
circles() {
let nb = button.HOWMANYCIRCLES;
for(let i=0;i<nb;i++) {
let circle = this.createCircle(i);
this.placeCircle(circle);
let size = this.resizeCircle(circle);
this.circlesSizes[i] = size;
this.resizeShadow(circle, size);
}
this.updateShadows();
}
createCircle(i) {
// Wrap
let element = this.create('circle');
element.classList.add('c-' + i);
// Body
let circleBody = this.create('circle-body');
// Shadow
let shadow = this.create('shadow');
// Blured shadow
let shadowBlur = this.create('shadow-blur');
// Insertion
let wrapShadow = document.createElement('div');
wrapShadow.classList.add('wrap-shadow');
wrapShadow.appendChild(shadow);
wrapShadow.appendChild(shadowBlur);
element.appendChild(circleBody);
element.appendChild(wrapShadow);
document.body.appendChild(element);
return document.querySelector('.c-'+i);
}
placeCircle(circle) {
let topValue = this.giveRandomPoxY();
let top = topValue + 'px';
let leftValue = this.giveRandomPoxX();
let left = leftValue + 'px';
circle.style.top = top;
circle.style.left = left;
this.circlesPos.push({top : topValue, left : leftValue});
}
resizeCircle(circle) {
let size = Math.floor( Math.random() * 30 ) + 20;
let before = circle.querySelector(".circle-body");
before.style.height = size+'px';
before.style.width = size+'px';
before.style.borderRadius = (size/2)+'px';
return size;
}
resizeShadow(circle, size) {
let wrapShadow = circle.querySelector('.wrap-shadow');
wrapShadow.style.width = size+'px';
wrapShadow.style.top = (size/2)+'px';
}
giveRandomPoxY() {
let buttonHeight = 50;
let output = Math.floor( Math.random() * window.innerHeight );
if ( output <= (window.innerHeight/2 + buttonHeight) && output >= (window.innerHeight/2 - buttonHeight) ) {
return this.giveRandomPoxY();
}
else {
return output;
}
}
giveRandomPoxX() {
let buttonWidth = 167;
let output = Math.floor( Math.random() * window.innerWidth );
if ( output <= (window.innerWidth/2 + buttonWidth) && output >= (window.innerWidth/2 - buttonWidth) ) {
return this.giveRandomPoxX();
}
else {
return output;
}
}
updateShadows() {
this.updateDistScores();
let nb = button.HOWMANYCIRCLES;
for(let i=0;i<nb;i++) {
let circle = document.querySelector('.c-' + i);
let shadow = circle.getElementsByClassName('wrap-shadow')[0];
let blurShadow = circle.querySelector('.shadow-blur');
let buttonX, buttonY;
[buttonX, buttonY] = this.getButtonPos(true);
let top = this.circlesPos[i]['top'];
let left = this.circlesPos[i]['left'];
this.rotatingShadow(left, top, buttonX, buttonY, shadow);
this.flaredShadow(this.dists[i], shadow);
this.lengthShadow(this.dists[i], shadow);
this.bluredShadow(this.dists[i], blurShadow);
this.brightnessCircle(this.dists[i], circle);
}
}
rotatingShadow(left, top, buttonX, buttonY, shadow) {
let tempX = top - buttonY;
let tempY = buttonX - left;
let angle = this.calcAngles(tempX, tempY);
shadow.style.transform = 'rotate(' + angle + 'deg)';
}
flaredShadow(dist, shadow) {
let deg = dist * 82 / 500;
deg = deg>82 ? 82 : deg;
shadow.style.transform += ' perspective(600px) rotateX(' + deg + 'deg)';
}
lengthShadow(dist, shadow) {
shadow.style.height = dist + 'px';
}
bluredShadow(dist, blurShadow) {
if ( dist<=200 ) {
let temp = dist/200;
let blur = temp*4;
blurShadow.style.filter = "blur(" + blur + "px)";
blurShadow.style.opacity = temp;
}
}
brightnessCircle(dist, circle) {
if (this.lightMode) {
let brightness = 800-dist;
if (brightness>0) {
brightness = brightness/8;
circle.style.filter = 'brightness(' + brightness + '%)';
}
}
}
calcAngles(x, y) {
return Math.atan2(y, x) * 180 / Math.PI;
}
over (e) {
this.lightMode = true;
this.handleMessage(true);
this.handleLightButton(true);
this.handleBackLight(true);
this.handleCircleBrightness(true);
this.updateShadows();
}
out (e) {
if (!this.moveMode) {
this.lightMode = false;
this.handleMessage(false);
this.handleLightButton(false);
this.handleBackLight(false);
this.handleCircleBrightness(false);
this.turnOffCirclesLights();
}
}
handleCircleBrightness(action) {
let method = action ? 'add' : 'remove';
let circles = document.getElementsByClassName('circle');
Array.from(circles).forEach(c=>c.classList[method]('circle-on'));
}
handleMessage (action) {
let method = action ? 'add' : 'remove';
this.message.classList[method]('up');
this.message2.classList[method]('up');
}
handleLightButton (action) {
let method = action ? 'add' : 'remove';
this.target.classList[method]('button-on');
}
handleBackLight (action) {
let method = action ? 'add' : 'remove';
this.light.classList[method]('lighted-zone-on');
}
turnOffCirclesLights() {
this.circlesPos.forEach((c, i)=>{
let circle = document.querySelector('.c-'+i);
circle.style.filter = "brightness(0%)";
});
}
getButtonPos(centered) {
let buttonX = this.target.style.left || this.targetPos.left;
let buttonY = this.target.style.top || this.targetPos.top;
if (typeof(buttonX)==='string') { buttonX = parseInt( buttonX.slice(0, buttonX.length-2) ); }
if (typeof(buttonY)==='string') { buttonY = parseInt( buttonY.slice(0, buttonY.length-2) ); }
return centered ? [buttonX + 83.5, buttonY] : [buttonX, buttonY];
}
clic (e) {
this.moveMode = true;
let buttonX, buttonY;
[buttonX, buttonY] = this.getButtonPos(false);
this.catchPointX = this.posX - buttonX;
this.catchPointY = this.posY - buttonY;
}
endclic (e) {
this.moveMode = false;
}
mouseOutWindow(e) {
if(this.moveMode && e.toElement.nodeName == 'HTML') {
this.moveMode = false;
this.out();
}
}
mousemove (e) {
if (!this.moveMode) {
this.posX = e.clientX;
this.posY = e.clientY;
}
else {
if (!this.collisionTest(e)) {
this.posX = e.clientX;
this.posY = e.clientY;
this.moveBox();
}
else {
this.posX = e.clientX;
this.posY = e.clientY;
}
}
}
collisionTest(e) {
let left, top;
[left, top] = this.getBoxPosFromMousePos(e.clientX, e.clientY);
let bigObjectExtremities = this.getExtremities(top, left, 50, 167);
let closeCircles = this.getCloseCircles();
let results = closeCircles.map(i=>{
let top, left;
({top, left} = this.circlesPos[i]);
let size = this.circlesSizes[i];
let smallObjectExtremities = this.getExtremities(top, left, size);
return this.compareExtremities(smallObjectExtremities, bigObjectExtremities);
});
return results.some(Boolean);
}
compareExtremities(small, big) {
let verticalSuperposition = (small, big) => {
return ((big.maxTop <= small.maxTop) && (big.maxBottom >= small.maxTop)) || ((big.maxTop <= small.maxBottom) && (big.maxBottom >= small.maxBottom));
};
let horizontalSuperposition = (small, big) => {
return (small.maxLeft <= big.maxRight) && (small.maxLeft >= big.maxLeft) || (small.maxRight >= big.maxLeft) && (small.maxRight <= big.maxRight);
};
if ( horizontalSuperposition(small, big) ) {
if ( verticalSuperposition(small, big) ) {
return true;
}
}
return false;
}
getExtremities(top, left, sizeH, sizeW) {
sizeW = sizeW || sizeH;
return {
maxTop : top,
maxBottom : top+sizeH,
maxLeft : left,
maxRight : left+sizeW
};
}
getCloseCircles() {
let closeCircles = this.dists.map((d,i)=>{
if (d<150) { return i; }
});
let result = closeCircles.filter(c=>{
if (typeof(c)==='number') {
return c.toString();
}
});
return result;
}
getBoxPosFromMousePos(mouseX,mouseY) {
let left = (mouseX-this.catchPointX);
let top = (mouseY-this.catchPointY);
return [left, top];
}
moveBox() {
let x, y;
[x, y] = this.getBoxPosFromMousePos(this.posX, this.posY);
this.target.style.left = x+'px';
this.target.style.top = y+'px';
this.moveLight();
this.updateShadows();
}
updateDistScores() {
this.dists = this.circlesPos.map((c, i)=>this.calcDist(i));
}
calcDist(i) {
let topC = this.circlesPos[i]['top'];
let leftC = this.circlesPos[i]['left'];
let topB, leftB;
[leftB, topB] = this.getButtonPos(true);
let tempTop = topB - topC;
let tempLeft = leftB - leftC;
let sqDist = tempTop**2 + tempLeft**2;
return Math.sqrt(sqDist);
}
moveLight() {
let buttonX, buttonY;
[buttonX, buttonY] = this.getButtonPos(true);
this.light.style.left = ( (buttonX+83.5) - window.innerWidth )+'px';
this.light.style.top = ( (buttonY+25) - window.innerHeight )+'px';
}
}
let pass = document.querySelector('#pass-intro');
let intro = document.querySelector('#intro');
pass.addEventListener('click', e=>{
intro.classList.add('intro-leaves');
new button();
});
// Future improvements
// class panel {
//
// dots = [];
//
// constructor() {
// this.dots = Array.from(document.getElementsByClassName('colored-dot'));
// this.events();
// }
//
// events() {
// this.dots.forEach(d=>d.addEventListener('click', (e)=>{
// this.clic(e.target);
// }));
// }
//
// clic(d) {
// let color = d.getAttribute('data-color');
// test(color);
// }
//
// }
// new panel();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.