A matter of gravity

A matter of gravity

更多有趣示例 尽在小红砖社区

示例

在这里插入图片描述

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);

猜你喜欢

转载自blog.csdn.net/weixin_45544796/article/details/107484121