有没有办法在 JS 中用 transform="translate(mouseX,mouseY)" 翻译嵌套的 <svg> 元素?

Is there a way to translate a nested <svg> element with transform="translate(mouseX,mouseY)" in JS?

编辑:代码:

jQuery.noConflict()
const turnEnum = ["Noughts", "Crosses"]
var turn = Math.round(Math.random()) ? turnEnum[0] : turnEnum[1];
var mouseX;
var mouseY;
var snapX;
var snapY;

jQuery(document).ready(function() {
  document.getElementById("turn-indicator").innerHTML = turn + "' turn!";
});

window.onload = function() {
  document.getElementById("game-board").addEventListener('mousemove', e => {
    var offset = jQuery("#game-board").offset();
    mouseX = Math.round(e.pageX - offset.left);
    mouseY = Math.round(e.pageY - offset.top);
    snapX = calcSnap(mouseX, 100);
    snapY = calcSnap(mouseY, 100);
    // document.getElementById("snap-icon-x-container").transform.baseVal.initialize(document.getElementById("snap-icon-x").viewportElement.createSVGTransform.setTranslate("mouseX, mouseY"));

    document.getElementById("snap-icon-x-container").setAttribute("transform", `translate(${mouseX}, ${mouseY})`);
  })
};


function calcSnap(val, gridSize) {
  var snap_candidate = gridSize * Math.round(val / gridSize);
  if (Math.abs(val - snap_candidate) < 50) {
    return snap_candidate;
  } else {
    return 0;
  }
}

function afterplay() {
  jQuery("#title-screen").remove();
  jQuery("#after-play").removeClass("invisible");
}

function play() {
  jQuery("#title-screen").fadeOut(300, afterplay);
}
#snap-icon-x {
  transform-origin: 3.5px 3.5px;
  transform: scale(10);
  -ms-transform: scale(10);
  -webkit-transform: scale(10);
  z-index: 10000;
}

#snap-icon-circle {
  transform-origin: -2px -2px;
  transform: scale(5);
  -ms-transform: scale(5);
  -webkit-transform: scale(5);
  z-index: 10000;
}

#game-board {
  z-index: 5;
}

#snap-icon-x-container {
  position: relative;
}

#snap-icon-circle-container {
  position: relative;
}
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
  <link rel="stylesheet" href="style.css">

  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
  <script src="script.js" type="text/javascript"></script>
  <title>Noughts and Crosses</title>
</head>

<body>
  <div id="game-container" class="border border-2 rounded-3 game-container d-flex flex-column mt-4">
    <div id="title-screen" class="d-flex flex-column">
      <h1 class="p-4 d-flex justify-content-center user-select-none">Noughts and Crosses</h1>
      <button class="p-4 col-6 mb-5 mx-auto btn btn-outline-dark btn-lg" id="playButton" onclick="play()">Play</button>
    </div>
    <div class="d-flex justify-content-center invisible p-5 flex-column" id="after-play">
      <h1 class="mx-auto user-select-none" id="turn-indicator">{}' turn!</h1>
      <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" id="game-board" class="w-auto mx-auto">
                <line stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel" id="svg_1" y2="300" x2="100"
                    y1="0" x1="100" stroke="#9b9b9b" fill="none" />
                <line stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel" id="svg_2" y2="300" x2="200"
                    y1="0" x1="200" stroke="#9b9b9b" fill="none" />
                <line stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel" id="svg_3" y2="100" x2="0"
                    y1="100" x1="300" stroke="#9b9b9b" fill="none" />
                <line stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel" id="svg_4" y2="200" x2="0"
                    y1="200" x1="300" stroke="#9b9b9b" fill="none" />
                <svg id="snap-icon-x-container" transform="translate(0, 0)">
                    <path
                        d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"
                        id="snap-icon-x" />
                </svg>
      <svg id="snap-icon-circle-container" transform="translate(0, 0)">
                    <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
                        id="snap-icon-circle" class="invisible" />
                </svg>
      </svg>
    </div>
  </div>





  <footer class="navbar fixed-bottom d-flex justify-content-center fs-5 user-select-none">Made by circles.png.</footer>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous"></script>
</body>

</html>

注意:与 Creating a new SVGTransform object to append to SVGTransformList 相似但不是重复,因为我无法评论 xD

并且我想将 SVG 中的一个 SVG 移动到我的鼠标位置。问题是,#snap-icon-x-container 元素似乎没有 viewportElement。在 Creating a new SVGTransform object to append to SVGTransformList 中,它有来自 RBarryYoung 的评论说我应该使用 svg.viewportElementsvg 指的是什么(我已经尝试过元素)和(不相关)为什么 viewportElement 没有出现在 VSCode ctrl-space菜单?

您可以转换嵌套 svg 内的路径:

在评论中提到,我通过更改路径的 d 属性编辑了答案,使十字变得可见。

//console.log(kk.getBBox())




//the main svg element
let svg = document.querySelector("svg");
//the path inside the nested svg
let sic = document.querySelector("#snap-icon-x-container path");
//a function to detect the mouse position on the svg - especially useful when the svg has a viewBox and no width and height. Also useful in this case
function oMousePosSVG(e) {
  var p = svg.createSVGPoint();
  p.x = e.clientX;
  p.y = e.clientY;
  var ctm = svg.getScreenCTM().inverse();
  var p = p.matrixTransform(ctm);
  return p;
}
//add a transform attribute to the path inside the nested svg
svg.addEventListener("mousemove", (e) => {
  let m = oMousePosSVG(e);
  sic.setAttribute("transform", `translate(${m.x},${m.y})`);
});
svg{border:1px solid}
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" id="game-board" class="w-auto mx-auto">
    <svg id="snap-icon-x-container">
    <path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z" id="snap-icon-x" />
    </svg>
</svg>

由于编辑后您的问题发生了变化,我添加了一个新答案。我希望这是你需要的:

我向 svg 添加了 9 个矩形,即您要单击的单元格。 对于每个矩形,我都添加了一个事件侦听器:单击时,十字出现在单击的矩形中。为了知道位置,我使用矩形的 x 和 y 属性的值 + 25

请注意嵌套的 svg 元素 (#cross) 有一个 viewBox a x a y a width 和 a height 属性。此外,我没有翻译 svg 元素,而是更改了 x 和 y 属性。

let rects = document.querySelectorAll("rect");
let cross = document.querySelector("#cross")

rects.forEach(r => {
  r.addEventListener("click",(e)=>{
  let x = Number(r.getAttribute("x")) || 0;
  let y = Number(r.getAttribute("y")) || 0;
  
  cross.setAttribute("x",x+25);  
  cross.setAttribute("y",y+25);  

  })
})
<svg width="300" height="300" id="game-board" class="w-auto mx-auto">
  <g stroke-width="3" stroke="#9b9b9b">
  <line y2="300" x2="100" y1="0" x1="100" stroke="#9b9b9b" />
  <line stroke-width="3" y2="300" x2="200" y1="0" x1="200" />
  <line stroke-width="3" y2="100" x2="0" y1="100" x1="300" />
  <line stroke-width="3" y2="200" x2="0" y1="200" x1="300" />
  </g>
  <rect width="100" height="100" />
  <rect x="100" width="100" height="100" />
  <rect x="200" width="100" height="100" />
  <rect y="100" width="100" height="100" />
  <rect y="100" x="100" width="100" height="100" />
  <rect y="100" x="200" width="100" height="100" />
  <rect y="200" width="100" height="100" />
  <rect y="200" x="100" width="100" height="100" />
  <rect y="200" x="200" width="100" height="100" />

  <line stroke-width="3" y2="300" x2="100" y1="0" x1="100" stroke="#9b9b9b" fill="none" />
  <line stroke-width="3" y2="300" x2="200" y1="0" x1="200" stroke="#9b9b9b" fill="none" />
  <line stroke-width="3" y2="100" x2="0" y1="100" x1="300" stroke="#9b9b9b" fill="none" />
  <line stroke-width="3" y2="200" x2="0" y1="200" x1="300" stroke="#9b9b9b" fill="none" />

  <svg id="cross" viewBox="4 4 8 8" width="50" height="50" x="25" y="25">
    <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" stroke="silver" />
  </svg>

</svg>