Поместите объект между двумя другими

Я новичок в canvas и хочу переместить свою анимацию DOM (HTML+JS) в canvas. Я пытался имитировать анимацию солнечной системы в основных анимациях - Web API Interfaces | МДН и мне удалось это сделать. Скрипка

У меня проблема с globalCompositeOperation, В анимации земля отбрасывает тень (прямоугольник с низкой непрозрачностью), и когда луна вращается вокруг этой темной области, луна должна находиться под теневым прямоугольником. Если я использую

context.globalCompositeOperation = 'destination-over';

на вершине draw Функция, я получаю ожидаемый результат ( скрипка), но затем Луна и Земля вращается ниже линий орбиты. Я не хочу этого Я хочу, чтобы и Земля, и Луна находились над линиями орбит, а Луна была под теневым прямоугольником. Есть способ сделать это? Если нет, мне придется избавиться от тени.

Я не пользовалась context.save() а также context.restore() в моем коде, хотя они используются в оригинальной анимации. Я не знаком с ними. Может быть связано с моей проблемой...

var sun = {
  radius : 80,
  x  : 0,
  y  : 0,
};
var earth = {
  radius : 20,
  x  : sun.radius + 80,
  y  : 0,
  angle : 0,
};
var moon = {
  radius : 8,
  x  : 50,
  y  : 0,
  angle : 0,
};

function draw() {
  var canvas = document.getElementById("canvas");
  var context = canvas.getContext("2d");

  context.resetTransform();
  context.clearRect(0, 0, canvas.width, canvas.height)
  context.translate(canvas.width/2, canvas.height/2);

  // sun
  context.beginPath();
  context.arc(0, 0, sun.radius, 0, 360 * Math.PI/180, false);
  context.fillStyle = "yellow";
  context.fill();

  // sun orbit
  context.beginPath();
  context.arc(0, 0, earth.x, 0, Math.PI*2, false);
  context.strokeStyle = "rgba(180,150,0,.25)";
  context.stroke();

  // earth
  earth.angle += .2;
  if (earth.angle == 360) {
    earth.angle = 0;
  }
  context.globalCompositeOperation = 'source-over'; // earth is above the sun and its orbit
  context.rotate(earth.angle * Math.PI/180);
  context.translate(earth.x, 0);
  context.beginPath();
  context.arc(0, 0, earth.radius, 0, 360*(Math.PI/180), false);
  context.fillStyle = "royalblue";
  context.fill();
  context.globalCompositeOperation = 'destination-over'; // earth's orbit and shadow are below the earth

  // earth shadow
  context.fillStyle = "rgba(0,0,0,0.1)";
  context.fillRect(0, -earth.radius, earth.radius*16, earth.radius*2);

  // earth orbit
  context.beginPath();
  context.arc(0, 0, moon.x, 0, Math.PI*2, false);
  context.strokeStyle = "rgba(0,0,200,.2)";
  context.stroke();

  // moon
  moon.angle += 1;
  if (moon.angle == 360) {
    moon.angle = 0;
  }
  context.globalCompositeOperation = 'source-over'; // moon is above the earth's orbit and shadow
  context.rotate(moon.angle * Math.PI/180);
  context.translate(moon.x, 0);
  context.beginPath();
  context.arc(0, 0, moon.radius, 0, 360 * Math.PI/180, false);
  context.fillStyle = "silver";
  context.fill();

  requestAnimationFrame(draw);
}

draw();
html {
  background: rgb(230,230,230);
}
canvas {
  background: white;
  border: 1px solid rgba(0,0,0,.2);
  box-shadow: 0 0 5px rgba(0,0,0,.1);
}
<canvas id="canvas" width="640" height="480"></canvas>

1 ответ

Решение

Вам просто нужно немного изменить порядок вещей в коде - составные режимы действительно не нужны в этом случае:

  • Все статические элементы, такие как солнце и земная орбита, могут быть визуализированы один раз и установлены, например, как фон для элемента.
  • Сначала нарисуйте орбиту Луны
  • Нарисуй вторую луну
  • Нарисуй тень третью
  • Наконец нарисуйте землю.

В этом обновлении я не пересчитывал вращение и т. Д., А использовал сохранение / восстановление.

var sun = {
    radius : 80,
    x  : 0,
    y  : 0,
};
var earth = {
    radius : 20,
    x  : sun.radius + 80,
    y  : 0,
    angle : 0,
};
var moon = {
    radius : 8,
    x  : 50,
    y  : 0,
    angle : 0,
};

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

context.setTransform(1,0,0,1,canvas.width/2, canvas.height/2);

// sun
context.beginPath();
context.arc(0, 0, sun.radius, 0, 360 * Math.PI/180, false);
context.fillStyle = "yellow";
context.fill();

// sun orbit
context.beginPath();
context.arc(0, 0, earth.x, 0, Math.PI*2, false);
context.strokeStyle = "rgba(180,150,0,.25)";
context.stroke();

canvas.style.backgroundImage = "url(" + canvas.toDataURL() + ")";

function draw() {

    context.setTransform(1,0,0,1,0,0);    
    context.clearRect(0, 0, canvas.width, canvas.height)
    context.setTransform(1,0,0,1,canvas.width/2, canvas.height/2);
    
    // earth
    earth.angle += .2;
    if (earth.angle == 360) {
        earth.angle = 0;
    }
    context.rotate(earth.angle * Math.PI/180);
    context.translate(earth.x, 0);

        // earth orbit
    context.beginPath();
    context.arc(0, 0, moon.x, 0, Math.PI*2, false);
    context.strokeStyle = "rgba(0,0,200,.2)";
    context.stroke();
    
    // moon
    context.save();
    moon.angle += 1;
    if (moon.angle == 360) {
        moon.angle = 0;
    }
    context.rotate(moon.angle * Math.PI/180);
    context.translate(moon.x, 0);
    context.beginPath();
    context.arc(0, 0, moon.radius, 0, 360 * Math.PI/180, false);
    context.fillStyle = "silver";
    context.fill();
    context.restore();

    // earth shadow
    context.fillStyle = "rgba(0,0,0,0.1)";
    context.fillRect(0, -earth.radius, earth.radius*16, earth.radius*2);

    context.beginPath();
    context.arc(0, 0, earth.radius, 0, 360*(Math.PI/180), false);
    context.fillStyle = "royalblue";
    context.fill();
    
    
    requestAnimationFrame(draw);
}

draw();
html {
    background: rgb(230,230,230);
}
canvas {
    background: white;
    border: 1px solid rgba(0,0,0,.2);
    box-shadow: 0 0 5px rgba(0,0,0,.1);
}
    <canvas id="canvas" width="640" height="480"></canvas>

Другие вопросы по тегам