Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <!-- arrow describing a clockwise rotation -->
<svg style="display: none;" viewBox="0 0 100 100" width="200" height="200">
    <g id="arrow-right" stroke="currentColor" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">
        <g transform="translate(50 50) rotate(-45)">
            <path fill="none" d="M 0 -30 a 30 30 0 0 1 0 60"></path>
            <path fill="currentColor" transform="translate(-20 30) rotate(90)" d="M 0 0 l -14 -14 h 28 z"></path>
        </g>
    </g>
</svg>

<!-- include a container with two buttons, to rotate the canvas clockwise and counter-clockwise -->
<div class="controls">
    <button data-direction="left">
        <span>Left</span>
        <!-- use the same arrow, but flip the use element around the y axis to describe a counter-clockwise rotation -->
        <svg viewBox="0 0 100 100" width="80" height="80">
            <g transform="translate(50 50) scale(-1 1)">
                <g transform="translate(-50 -50)">
                    <use href="#arrow-right"></use>
                </g>
            </g>
        </svg>
    </button>

    <h2>Rotate</h2>

    <button data-direction="right">
        <span>Right</span>
        <svg viewBox="0 0 100 100" width="80" height="80">
            <use href="#arrow-right"></use>
        </svg>
    </button>
</div>
              
            
!

CSS

              
                @import url("https://fonts.googleapis.com/css?family=Poppins:300,800&display=swap");

* {
    box-sizing: border-box;
    padding: 0;
    margin: 0;
}
body {
    min-height: 100vh;
    background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="150" height="142.5"><g transform="translate(25 23.75)" opacity="0.1" stroke="hsl(0, 0%, 50%)" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(50 47.5) rotate(60)"><g transform="translate(-50 -47.5)"><path fill="none" d="M65 5a30 30 0 010 60"/><path fill="currentColor" d="M45 65l14-14v28z"/><g><path fill="none" d="M35 90a30 30 0 010-60"/><path fill="currentColor" d="M55 30L41 44V16z"/></g></g></g></g></svg>'),
        #fbfbfb;
    background-size: 10%;
    color: hsl(0, 0%, 11%);
    font-family: "Poppins", sans-serif;
    /* center the contents horizontally */
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 0.5rem 0;
}
/* display the controls in a row, pushing the buttons on either side */
.controls {
    width: 90vw;
    max-width: 500px;
    margin: 0.5rem 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
h2 {
    text-transform: uppercase;
    font-weight: 300;
    font-size: 1.75rem;
}
button {
    font-weight: 800;
    text-align: center;
    font-family: inherit;
    color: inherit;
    font-size: 1.25rem;
    background: none;
    border: none;
    outline: none;
}
button svg {
    display: block;
    width: 3.5em;
    height: auto;
}
/* when the button is focused underline the span element */
button:focus span {
    text-decoration: underline;
}
/* when the button is hovered or being focused on, rotate the nested svg
! only as the hover/focus occurs
*/
button:hover svg,
button:focus svg {
    transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1);
    transform: rotate(1turn);
}
/* rotate the first svg in the opposite direction */
button:first-of-type:hover svg,
button:first-of-type:focus svg {
    transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1);
    transform: rotate(-1turn);
}
/* add a transition to the canvas to rotate the element smoothly */
canvas {
    width: 90vw;
    max-width: 450px;
    display: block;
    transition: transform 1s cubic-bezier(0.445, 0.05, 0.55, 0.95);
}

              
            
!

JS

              
                const { Engine, Render, World, Bodies, Body, Events } = Matter;

// engine
const engine = Engine.create();

// size of the canvas and the nested player
const width = 500;
const height = 500;
const size = 50;
const w = 30;
const padding = 10;

// renderer
const render = Render.create({
  element: document.body,
  engine,
  options: {
    wireframes: false,
    background: 'hsl(0, 0%, 95%)',
    width,
    height,
  },
});

// border around the canvas
const line = (x, y, w, h) =>
  Bodies.rectangle(x, y, w, h, {
    render: {
      fillStyle: 'hsl(0, 0%, 11%)',
    },
  });

const borderTop = line(width / 2, 0, width, size);
const borderRight = line(width, height / 2, size, height);
const borderBottom = line(width / 2, height, width, size);
const borderLeft = line(0, height / 2, size, height);

const d = size * 1.5 + w / 2 + padding;
const gate1 = line(width / 5, height - d, width / 2.5, w);
const gate2 = line(width - size * 2, height / 5, w, height / 2.5);
const gate3 = line(width - width / 6, height - d, width / 3, w);
const gate4 = line(width / 4, d, width / 2, w);
const gate5 = line(d, height / 1.5, w, height / 4);

// goal post
// the idea is to position the shape at random in the corners of the canvas
const coordinates = [
  [size + padding / 2, size + padding / 2],
  [width - (size + padding / 2), size + padding / 2],
  [width - (size + padding / 2), height - (size + padding / 2)],
  [size + padding / 2, height - (size + padding / 2)],
];
const randomItem = arr => arr[Math.floor(Math.random() * arr.length)];

const goal = Bodies.rectangle(...randomItem(coordinates), size, size, {
  render: {
    fillStyle: 'hsl(0, 60%, 55%)',
  },
  // is sensor to prevent a collision
  isSensor: true,
  // label matching the shape subject to gravity
  label: 'match',
});

const grid = Body.create({
  parts: [
    borderTop,
    borderRight,
    borderBottom,
    borderLeft,
    gate1,
    gate2,
    gate3,
    gate4,
    gate5,
    goal,
  ],
  isStatic: true,
});

// shape subject to the canvas's gravity
const player = Bodies.circle(width / 2, height / 2, size / 2, {
  render: {
    fillStyle: 'hsl(120, 65%, 60%)',
  },
  label: 'match',
});

// add the elements to the world
const { world } = engine;
World.add(world, [grid, player]);

// run the engine
Engine.run(engine);

// run the render
Render.run(render);

// variable keeping track of the rotation (number of times the canvas is meant to rotate clockwise and counter-clockwise)
let rotation = 0;
// possible gravity values
// the idea is to have the rotation affect the canvas element, while the world updates its gravity with the four values
let index = 0;
const gravity = [[0, 1], [1, 0], [0, -1], [-1, 0]];

// rotate the canvas and update the gravity according to the directions passed as argument
// for the gravity, the idea is to go through the array in order, and reach for the beginning/end of the array when going after the end/before the beginning
function rotateMaze(direction) {
  const { canvas } = render;
  if (direction === 'left') {
    rotation -= 1;
    index = index <= 0 ? gravity.length - 1 : index - 1;
  } else {
    rotation += 1;
    index = index >= gravity.length - 1 ? 0 : index + 1;
  }
  canvas.style.transform = `rotate(${rotation * 90}deg)`;

  const [x, y] = gravity[index];
  world.gravity.x = x;
  world.gravity.y = y;
}

// following a click event extract the data-direction attribute and call the rotate function with the appropriate direction
const buttons = document.querySelectorAll('button');
function handleClick() {
  const direction = this.getAttribute('data-direction');
  rotateMaze(direction);
}
buttons.forEach(button => button.addEventListener('click', handleClick));

// following a collisionStart event, check if the collision occurs between the player and the goal post
function handleCollision(e) {
  const { pairs } = e;
  pairs.forEach(pair => {
    const { label: labelA } = pair.bodyA;
    const { label: labelB } = pair.bodyB;
    if (labelA === labelB) {
      // momentarily change the color of the goal post before changing the coordinates of the goal post and the player's shape
      goal.render.fillStyle = 'hsl(120, 65%, 60%)';
      const timeout = setTimeout(() => {
        goal.render.fillStyle = 'hsl(0, 60%, 55%)';

        const [x, y] = randomItem(coordinates);
        Body.setPosition(goal, {
          x,
          y,
        });
        Body.setPosition(player, {
          x: width / 2,
          y: height / 2,
        });

        clearTimeout(timeout);
      }, 500);
    }
  });
}
Events.on(engine, 'collisionStart', handleCollision);

              
            
!
999px

Console