How to make a moving/sliding element in Three.js?

I discovered three.js which I love to use now, but I built a rotating car animation with keyboard arrows to control it, but I can’t find out how to make a sliding animation (e.g.,

-> -> ->

) Instead of a rotation animation. I tried searching but I don’t even know how to explain it other than a sliding animation. Does someone know how / if you can do this?

Here is my code:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>ThreeJS Animation</title>
        <style>            
            body {
              margin: 0;
              font-weight: 100;
              float: none;
              overflow: hidden;
            }
            canvas {
              width: 100%;
              height: 100%;
              margin: 0;
              padding: 0;
            }
        </style>
    </head>
    <body>
        <script src="https://threejs.org/build/three.js"></script>
        <script>
let carBottomColor = "#999";
let carTopColor = "#FFF";
let carWindowColor = "#666";
const scene = new THREE.Scene();
scene.background = new THREE.Color("#f1f1f1");
const car = createCar();
scene.add(car);

const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(200, 500, 300);
scene.add(dirLight);

const aspectRatio = window.innerWidth / window.innerHeight;
const cameraWidth = 300;
const cameraHeight = cameraWidth / aspectRatio;
const camera = new THREE.OrthographicCamera(
  cameraWidth / -2, // left
  cameraWidth / 2, // right
  cameraHeight / 2, // top
  cameraHeight / -2, // bottom
  200, // near plane
  800 // far plane
);
camera.position.set(200, 200, 200);
camera.lookAt(0, 10, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);

let x = 0;
let y = 0;
let keydown = '';
document.body.addEventListener('keydown', e => {
  keydown = e.key;
});
document.body.addEventListener('keyup', e => {
  keydown = '';
});
const update = () => {
    switch (keydown) {
    case 'ArrowUp':
      x -= 0.1;
      car.rotation.x = x;
      break;
    case 'ArrowDown':
      x += 0.1;
      car.rotation.x = x;
      break;
    case 'ArrowLeft':
      y -= 0.1;
      car.rotation.y = y;
      break;
    case 'ArrowRight':
      y += 0.1;
      car.rotation.y = y;
      break;
  }
  window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);


renderer.setAnimationLoop(() => {
  renderer.render(scene, camera);
});

document.body.appendChild(renderer.domElement);

function createCar() {
  const car = new THREE.Group();
  const backWheel = createWheels();
  backWheel.position.y = 6;
  backWheel.position.x = -18;
  car.add(backWheel);

  const frontWheel = createWheels();
  frontWheel.position.y = 6;
  frontWheel.position.x = 18;
  car.add(frontWheel);

  const main = new THREE.Mesh(
    new THREE.BoxBufferGeometry(60, 15, 30),
    new THREE.MeshLambertMaterial({ color: carBottomColor })
  );
  main.position.y = 12;
  car.add(main);

  const carFrontTexture = getCarFrontTexture();

  const carBackTexture = getCarFrontTexture();

  const carRightSideTexture = getCarSideTexture();

  const carLeftSideTexture = getCarSideTexture();
  carLeftSideTexture.center = new THREE.Vector2(0.5, 0.5);
  carLeftSideTexture.rotation = Math.PI;
  carLeftSideTexture.flipY = false;

  const cabin = new THREE.Mesh(new THREE.BoxBufferGeometry(33, 12, 24), [
    new THREE.MeshLambertMaterial({ map: carFrontTexture }),
    new THREE.MeshLambertMaterial({ map: carBackTexture }),
    new THREE.MeshLambertMaterial({ color: carTopColor }), // top
    new THREE.MeshLambertMaterial({ color: carTopColor }), // bottom
    new THREE.MeshLambertMaterial({ map: carRightSideTexture }),
    new THREE.MeshLambertMaterial({ map: carLeftSideTexture })
  ]);
  cabin.position.x = -6;
  cabin.position.y = 25.5;
  car.add(cabin);
  return car;
}

function createWheels() {
  const geometry = new THREE.BoxBufferGeometry(12, 12, 33);
  const material = new THREE.MeshLambertMaterial({ color: "#333" });
  const wheel = new THREE.Mesh(geometry, material);
  return wheel;
}

function getCarFrontTexture() {
  const canvas = document.createElement("canvas");
  canvas.width = 64;
  canvas.height = 32;
  const context = canvas.getContext("2d");

  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 64, 32);

  context.fillStyle = carWindowColor || carBaseColor; // Wheel color
  context.fillRect(8, 8, 48, 24);

  return new THREE.CanvasTexture(canvas);
}

function getCarSideTexture() {
  const canvas = document.createElement("canvas");
  canvas.width = 128;
  canvas.height = 32;
  const context = canvas.getContext("2d");

  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 128, 32);

  context.fillStyle = carWindowColor || carBaseColor; // Wheel color
  context.fillRect(10, 8, 38, 24);
  context.fillRect(58, 8, 60, 24);

  return new THREE.CanvasTexture(canvas);
}
        </script>
    </body>
</html>

Answer

I made some assumptions, but I think what you’re trying to do is rotate the car along the y-axis when pressing left/right arrows, and you’re trying to move the card forward when pressing up/down arrows. If that’s the case, then you can do car.translateX() to create that forward/backward motion, and car.rotation.y to make the car turn.

Here’s the modified example:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>ThreeJS Animation</title>
        <style>            
            body {
              margin: 0;
              font-weight: 100;
              float: none;
              overflow: hidden;
            }
            canvas {
              width: 100%;
              height: 100%;
              margin: 0;
              padding: 0;
            }
        </style>
    </head>
    <body>
        <script src="https://threejs.org/build/three.js"></script>
        <script>
let carBottomColor = "#999";
let carTopColor = "#FFF";
let carWindowColor = "#666";
const scene = new THREE.Scene();
scene.background = new THREE.Color("#f1f1f1");
const car = createCar();
scene.add(car);

const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(200, 500, 300);
scene.add(dirLight);

const aspectRatio = window.innerWidth / window.innerHeight;
const cameraWidth = 300;
const cameraHeight = cameraWidth / aspectRatio;
const camera = new THREE.OrthographicCamera(
  cameraWidth / -2, // left
  cameraWidth / 2, // right
  cameraHeight / 2, // top
  cameraHeight / -2, // bottom
  200, // near plane
  800 // far plane
);
camera.position.set(200, 200, 200);
camera.lookAt(0, 10, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);

let x = 0;
let y = 0;
let keydown = '';
document.body.addEventListener('keydown', e => {
  e.preventDefault();
  keydown = e.key;
});
document.body.addEventListener('keyup', e => {
  keydown = '';
});
const update = () => {
    switch (keydown) {
    case 'ArrowUp':
      car.translateX(1.0);
      break;
    case 'ArrowDown':
      car.translateX(-1.0);
      break;
    case 'ArrowLeft':
      y += 0.1;
      car.rotation.y = y;
      break;
    case 'ArrowRight':
      y -= 0.1;
      car.rotation.y = y;
      break;
  }
  window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);


renderer.setAnimationLoop(() => {
  renderer.render(scene, camera);
});

document.body.appendChild(renderer.domElement);

function createCar() {
  const car = new THREE.Group();
  const backWheel = createWheels();
  backWheel.position.y = 6;
  backWheel.position.x = -18;
  car.add(backWheel);

  const frontWheel = createWheels();
  frontWheel.position.y = 6;
  frontWheel.position.x = 18;
  car.add(frontWheel);

  const main = new THREE.Mesh(
    new THREE.BoxBufferGeometry(60, 15, 30),
    new THREE.MeshLambertMaterial({ color: carBottomColor })
  );
  main.position.y = 12;
  car.add(main);

  const carFrontTexture = getCarFrontTexture();

  const carBackTexture = getCarFrontTexture();

  const carRightSideTexture = getCarSideTexture();

  const carLeftSideTexture = getCarSideTexture();
  carLeftSideTexture.center = new THREE.Vector2(0.5, 0.5);
  carLeftSideTexture.rotation = Math.PI;
  carLeftSideTexture.flipY = false;

  const cabin = new THREE.Mesh(new THREE.BoxBufferGeometry(33, 12, 24), [
    new THREE.MeshLambertMaterial({ map: carFrontTexture }),
    new THREE.MeshLambertMaterial({ map: carBackTexture }),
    new THREE.MeshLambertMaterial({ color: carTopColor }), // top
    new THREE.MeshLambertMaterial({ color: carTopColor }), // bottom
    new THREE.MeshLambertMaterial({ map: carRightSideTexture }),
    new THREE.MeshLambertMaterial({ map: carLeftSideTexture })
  ]);
  cabin.position.x = -6;
  cabin.position.y = 25.5;
  car.add(cabin);
  return car;
}

function createWheels() {
  const geometry = new THREE.BoxBufferGeometry(12, 12, 33);
  const material = new THREE.MeshLambertMaterial({ color: "#333" });
  const wheel = new THREE.Mesh(geometry, material);
  return wheel;
}

function getCarFrontTexture() {
  const canvas = document.createElement("canvas");
  canvas.width = 64;
  canvas.height = 32;
  const context = canvas.getContext("2d");

  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 64, 32);

  context.fillStyle = carWindowColor || carBaseColor; // Wheel color
  context.fillRect(8, 8, 48, 24);

  return new THREE.CanvasTexture(canvas);
}

function getCarSideTexture() {
  const canvas = document.createElement("canvas");
  canvas.width = 128;
  canvas.height = 32;
  const context = canvas.getContext("2d");

  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 128, 32);

  context.fillStyle = carWindowColor || carBaseColor; // Wheel color
  context.fillRect(10, 8, 38, 24);
  context.fillRect(58, 8, 60, 24);

  return new THREE.CanvasTexture(canvas);
}
        </script>
    </body>
</html>