Skip to content

Instantly share code, notes, and snippets.

@rampol
Created June 23, 2018 15:26
Show Gist options
  • Save rampol/f37481202ef7247926196b5167b65227 to your computer and use it in GitHub Desktop.
Save rampol/f37481202ef7247926196b5167b65227 to your computer and use it in GitHub Desktop.
typhysics
<div class="stage" data-el="stage"></div>
<h1 data-el="head">
<span data-el="letter">M</span>
<span data-el="letter">A</span>
<span data-el="letter">T</span>
<span data-el="letter">T</span>
<span data-el="letter">E</span>
<span data-el="letter">R</span>
<span data-el="letter">J</span>
<span data-el="letter">S</span>
</h1>
<div class="bouncer" data-el="bouncer"></div>
<div class="bubbles" data-el="bubbles"></div>
var cursorX;
var cursorY;
var w = document.body.clientWidth;
var h = document.body.clientHeight;
var stage = document.querySelector('[data-el="stage"]');
var head = document.querySelector('[data-el="head"]');
var letters = head.querySelectorAll('[data-el="letter"]');
var blocks = [];
var borders = [];
var bouncer = document.querySelector('[data-el="bouncer"]');
var bouncerRadius = bouncer.clientWidth * 0.5;
var bouncerClone;
var bubbles = [];
var categories = {
catMouse: 0x0002,
catBody: 0x0004
}
initDim = {
w: document.body.clientWidth,
h: document.body.clientHeight
};
// ______________________________ Matter.js module aliases
var Engine = Matter.Engine,
World = Matter.World,
Body = Matter.Body,
Bodies = Matter.Bodies,
Render = Matter.Render,
Constraint = Matter.Constraint,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint;
// ______________________________ canvas element to draw into
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
// ______________________________ Matter engine
var engine = Engine.create( { enableSleeping: false });
engine.world.wireframes = false;
engine.world.gravity.x = 0;
engine.world.gravity.y = 0;
var renderer = Render.create({
element: stage,
canvas: canvas,
context: context,
engine: engine,
options: {
bounds: true,
showBounds: false,
background: "transparent",
width: w,
height: h,
wireframes: false
}
});
var initBubbles = function(){
if(!letters || letters.length == 0) return;
var bubbleContainer = document.querySelector('[data-el="bubbles"]');
for(var i = 0; i < letters.length*2; i++) {
var bubble = document.createElement('div');
bubble.classList.add('bubble');
bubble.dataset.active = 'false';
bubbleContainer.appendChild( bubble );
bubbles.push( bubble );
}
}
var animateBubble = function( x, y ){
function animate(el, x, y){
el.dataset.active = 'true';
TweenMax.set(el, {
x: x,
y: y
})
TweenMax.fromTo(el, 0.4, {
opacity: 1,
scaleX: 0,
scaleY: 0
}, {
opacity: 0,
scaleX: 1,
scaleY: 1,
ease: Power3.easeOut,
onComplete: function(){
el.style = '';
el.dataset.active = 'false';
}
})
};
for(var i = 0; i < bubbles.length; i++) {
if( bubbles[i].dataset.active == 'false'){
animate(bubbles[i], x, y);
break;
}
}
}
Matter.Events.on(engine, 'collisionStart', function(event) {
var pairs = event.pairs;
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
if( pair.bodyA.isStatic || pair.bodyB.isStatic ) return;
animateBubble(
pair.bodyA.position.x + (pair.bodyA.position.x - pair.bodyB.position.x) * -0.5,
pair.bodyA.position.y + (pair.bodyA.position.y - pair.bodyB.position.y) * -0.5
);
}
})
var updatePosition = function(){
requestAnimationFrame(updatePosition);
for(var i = 0; i < letters.length; i++) {
TweenLite.set( letters[i], {
x: blocks[i].position.x - letters[i].clientWidth*0.5 + 'px',
y: blocks[i].position.y - letters[i].clientWidth*0.5 + 'px',
rotation: blocks[i].angle + 'rad'
});
}
TweenLite.set( bouncer, {
x: bouncerClone.position.x - bouncer.clientWidth*0.5 + 'px',
y: bouncerClone.position.y - bouncer.clientHeight*0.5 + 'px',
rotation: bouncerClone.angle + 'rad'
});
}
// ______________________________ util
var updateCanvas = function(){
var diffX = document.body.clientWidth - w;
var diffY = document.body.clientHeight - h;
w = document.body.clientWidth;
h = document.body.clientHeight;
renderer.options.width = w;
renderer.options.height = h;
renderer.canvas.width = w;
renderer.canvas.height = h;
engine.world.bounds.max.x = window.width;
engine.world.bounds.max.y = window.height;
};
var initMouse = function (array){
var mouse = Matter.Mouse.create(canvas);
var mouseConstraint = MouseConstraint.create(engine, { mouse: mouse });
mouseConstraint.constraint.stiffness = 2;
World.add(engine.world, mouseConstraint);
Matter.Events.on(mouseConstraint, 'startdrag', removeInfo);
// catBody category objects should not be draggable with the mouse
mouseConstraint.collisionFilter.mask = 0x0002 | categories.catMouse;
}
// helpful function
// src > http://stackoverflow.com/a/35093569
var initEscapedBodiesRetrieval = function(allBodies, startCoordinates) {
function hasBodyEscaped(body) {
var x = body.position.x;
var y = body.position.y;
return x < 0 || x > w || y < 0 || y > h;
}
setInterval(function() {
var i, body;
for (i = 0; i < allBodies.length; i++) {
body = allBodies[i];
if (hasBodyEscaped(body)) {
Matter.Body.setVelocity(body, {
x: -body.velocity.x,
y: -body.velocity.y
});
Matter.Body.translate(body, {
x: ( startCoordinates.x - body.position.x ),
y: ( startCoordinates.y - body.position.y )
});
}
}
}, 300);
}
// ______________________________ create centered block
var fixBouncer = function(){
bouncer.style.position = 'absolute';
bouncer.style.top = '0';
bouncer.style.left = '0';
bouncer.style.marginTop = '0';
updatePosition();
};
var removeInfo = function(){
bouncer.classList.add('infoOut');
};
var initBouncer = function(){
bouncerClone = Bodies.circle(
bouncer.offsetLeft + bouncerRadius,
bouncer.offsetTop + bouncerRadius,
bouncerRadius, {
isSleeping: false,
density: 0.08,
collisionFilter: {
category: categories.catMouse
},
render: {
opacity: 0
}
});
World.add(engine.world, bouncerClone );
Matter.Events.on(engine.world, "afterAdd", fixBouncer);
}
var fixLetters = function(){
head.style.position = 'absolute';
head.style.top = '0';
head.style.left = '0';
for(var i = 0; i < letters.length; i++) {
letters[i].style.position = 'absolute';
letters[i].style.top = '0';
letters[i].style.left = '0';
}
};
var initLetterClones = function(){
for(var i = 0; i < letters.length; i++) {
blocks.push(
Bodies.rectangle(
head.offsetLeft + letters[i].offsetLeft + letters[i].clientWidth*0.5,
head.offsetTop + letters[i].offsetTop + letters[i].clientHeight*0.5,
letters[i].clientWidth,
letters[i].clientHeight, {
isSleeping: false,
density: 1,
restitution: 0.7,
frictionAir: 0.0001,
collisionFilter: {
category: categories.catBody
},
render: {
opacity: 0
}
})
);
World.add(engine.world, blocks[i]);
letters[i].style.width = letters[i].clientWidth + 'px';
letters[i].style.height = letters[i].clientHeight + 'px';
}
}
// walls/borders
var initBorders = function(){
var borderOptions = { isStatic: true, render: { opacity: 0 }};
var offset = 30;
borders.push(Bodies.rectangle( w*0.5, offset, w, 2, borderOptions )); // top
borders.push(Bodies.rectangle( w - offset, h*0.5, 2, h, borderOptions ));
borders.push(Bodies.rectangle( w*0.5, h - offset, w, 2, borderOptions )); // bottom
borders.push(Bodies.rectangle( 0 + offset, h*0.5, 2, h, borderOptions ));
for(var i = 0; i < borders.length; i++){
World.add(engine.world, borders[i]);
}
};
var updateBorders = function() {
for(var i = 0; i < borders.length; i++){
World.remove(engine.world, borders[i]);
}
borders = [];
initBorders();
}
var init = function(){
initBubbles();
initLetterClones();
initMouse(blocks);
initBouncer();
initEscapedBodiesRetrieval(blocks, { x: w*0.5, y: h*0.5 });
fixLetters();
initBorders();
Engine.run(engine);
Render.run(renderer);
}
// ======================= H E L P E R S / U T I L S
window.addEventListener('resize', resizeHandler);
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var resizeHandler = debounce(function() {
updateCanvas();
updateBorders();
}, 250);
// ______________________________ Helpers
function randArb(min, max) {
return Math.random() * (max - min) + min;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// ______________________________ F I R E
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/46992/matter.min.js"></script>
@import 'https://fonts.googleapis.com/css?family=Source+Code+Pro:900';
$dark: #012C45;
$light: #fff;
$frameOffset: 30px;
$sizeBubble: 70px;
*,
*:before,
*:after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
background: #333;
text-align: center;
box-shadow: inset 0 0 0 30px white;
cursor: -webkit-grab;
cursor: grab;
user-select: none;
}
body:active {
cursor: -webkit-grabbing;
cursor: grabbing;
}
.stage {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
}
h1 {
position: relative;
margin: 0 auto;
font-family: 'Source Code Pro', monospace;
font-size: 3rem;
line-height: 1.6rem;
color: rgba(white, 1);
display: flex;
flex-direction: row;
z-index: 3;
pointer-events: none;
-webkit-user-select: none;
user-select: none;
span {
position: relative;
display: block;
width: 1.8rem;
height: 2rem;
}
}
.bouncer {
position: relative;
margin: 5% auto 0 auto;
width: 60px;
height: 60px;
border-radius: 50%;
box-shadow: inset 0 0 0 7px white;
&:before {
content: '';
position: absolute;
bottom: -25px;
left: calc(50% - 5px);
width: 10px;
height: 10px;
border-top: 2px solid white;
border-right: 2px solid white;
transform: translateY(0%) rotate(-45deg);
transition: transform 0.3s, opacity 0.3s;
will-change: transform, opacity;
animation: bounce 0.5s infinite alternate-reverse;
}
&:after {
content: 'Drag and hit';
position: absolute;
bottom: -50px;
left: 50%;
text-align: center;
font-family: sans-serif;
font-size: 11px;
color: white;
letter-spacing: 2px;
text-transform: uppercase;
white-space: nowrap;
transform: translateX(-50%);
transition: transform 0.3s, opacity 0.3s;
will-change: transform, opacity;
}
}
@keyframes bounce {
to { transform: translateY(80%) rotate(-45deg); }
}
.bouncer.infoOut {
&:before {
opacity: 0;
}
&:after {
opacity: 0;
transform: translateX(-50%) translateY(100%);
}
}
.bubbles {
position: absolute;
top: 0;
left: 0;
}
.bubble {
position: absolute;
top: $sizeBubble * -0.5;
left: $sizeBubble * -0.5;
width: $sizeBubble;
height: $sizeBubble;
background-color: rgba(white, 0.7);
border-radius: 50%;
opacity: 0;
&:before,
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid white;
border-radius: 50%;
transform: scale(1);
}
&[data-active='false']:before { animation: none; }
&[data-active='false']:after { animation: none; }
&[data-active='true']:before { animation: explodeA 0.35s; }
&[data-active='true']:after { animation: explodeB 0.35s; }
}
@keyframes explodeA {
100% { transform: scale(1.5); }
}
@keyframes explodeB {
100% { transform: scale(2.5); }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment