Как ввести и использовать переменные в SVG и одновременно в JavaScript?

Попробовал сделать небольшую игру в SVG под контролем JS. Однако столкнулся с проблемой: как ввести и повторно использовать в коде SVG переменные? Здесь для определения ширины и высоты <svg>, начальной позиции "шаровой молнии" (<circle>), ширины граничной рамки <rect>. Затем как изящно получить их в JS?

Также буду рад любым замечаниям относительно как SVG, так и JS. Спасибо.

let svg = document.getElementById('_svg');
let circle = svg.querySelector('circle');
let text = svg.querySelector('text');

let position = [+circle.getAttribute('cx'), +circle.getAttribute('cy')];

let attractionX = 0;
let attractionY = 0;

let score = 0;

let anim;

svg.addEventListener('mousemove', e => {
    attractionX = position[0] < e.clientX ? 1 : -1;
    attractionY = position[1] < e.clientY ? 1 : -1;
})

function animateCircle() {
    position[0] += Math.floor(Math.random()*11) - 5 + attractionX;
    position[1] += Math.floor(Math.random()*11) - 5 + attractionY;
    score++;
    
    circle.setAttribute('cx', position[0]);
    circle.setAttribute('cy', position[1]);
    text.innerHTML = score;
    
    anim = requestAnimationFrame(animateCircle);
    
    if(position[0]<=20 || position[0]>=580 || position[1]<=20 || position[1]>=140){
        cancelAnimationFrame(anim);
        text.style.fill = 'red';
        text.style.fontSize = '20px';
        text.innerHTML = 'GAMEOVER. Your score: ' + score;
    }   
}

animateCircle();
<svg viewbox="0 0 600 160" id="_svg" width="600px" height="160px">
    <circle cx="300" cy="80" r="20" fill="yellow" stroke="black" />
    <rect x="0" y="0" width="600" height="160" stroke="red" fill="none"/>
    <text x="10" y="20">0</text>
</svg>


Ответы (1 шт):

Автор решения: Pavel Grishaev

В SVG нет переменных, как и в HTML. Их можно создать в JS и оттуда задавать SVG-элементам свойства. Следовательно считывать их не придётся, потому что переменные уже будут в JS.

Возможно вы хотели исходить из SVG, потому что так выглядит нагляднее. Но можно использовать SVG просто как компонент отображения, а не источник данных, а все сущности держать в JS. Так направление данных будет всегда от JS в SVG. Так строятся графики, онлайн-конструкторы и прочие динамичные вещи на SVG. У них данные всегда в JS или в JSON или в какой-то базе, но не в SVG.

На отдельных переменных можно разве что элементарное что-то сделать, тогда про изящность можно не задумываться.

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

class Game {
  
  svg;
  
  constructor(){
    this.svg = new Svg();
    
    // Просто куча кругов для примера удобства использования
    for( let i = 0; i < 10; i++ ){
      let circle = new Circle(
        Math.random() * this.svg.width,
        Math.random() * this.svg.height,
        Math.random() * 20
      );
      this.svg.append(circle);
    }
    
    // А этот круг за мышкой следует
    let circleForMouse = new Circle( 0, 0, 50 );
    this.svg.append(circleForMouse);
    
    this.svg.element.addEventListener( 'mousemove', event => {
      circleForMouse.x = event.clientX;
      circleForMouse.y = event.clientY;
    })
  }

}

class Svg {
  
  static xmlns = "http://www.w3.org/2000/svg";
  
  element;
  
  constructor(){
    this.element = Svg.createElement('svg');
    document.body.append(this.element);
    this.updateSize();
  }
  
  static createElement( tagName ){
    return document.createElementNS( Svg.xmlns, tagName );
  }
  
  updateSize(){
    let width = document.body.clientWidth;
    let height = document.body.clientHeight;
    this.viewbox = `0 0 ${width} ${height}`;
    this.width = width;
    this.height = height;
  }
  
  get viewbox(){
    return this.element.getAttribute('viewbox');
  }
  
  set viewbox( value ){
    this.element.setAttribute( 'viewbox', value );
  }
  
  get width(){
    return +this.element.getAttribute('width');
  }
  
  get height(){
    return +this.element.getAttribute('height');
  }
  
  set width( value ){
    this.element.setAttribute( 'width', value );
  }
  
  set height( value ){
    this.element.setAttribute( 'height', value );
  }
  
  append( childInstance ){
    this.element.append( childInstance.element );
  }
  
}

class Circle {
  
  element;
  
  constructor( x, y, radius ){
    this.element = Svg.createElement('circle');
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
  
  get x(){
    return +this.element.getAttribute('cx');
  }
  
  get y(){
    return +this.element.getAttribute('cy');
  }
  
  set x( value ){
    this.element.setAttribute( 'cx', value );
  }
  
  set y( value ){
    this.element.setAttribute( 'cy', value );
  }
  
  get radius(){
    return +this.element.getAttribute('r');
  }
  
  set radius( value ){
    this.element.setAttribute( 'r', value );
  }
  
}

let game = new Game();
svg {
  border: 1px solid black;
}
<!-- заметьте, что в HTML ничего нет, всё в JS -->

→ Ссылка