イベント周り
基本的に getBBox を利用すること。
MouseEvent はブラウザのページの座標が取れてくるが、SVG 内の座標系にそのまま代入してしまうと、viewBox がピクセルと一致していない場合や transform がかかっている場合に座標がずれてしまう。
Demo
ハチャメチャな transform がかかっている場合でも正常にドラッグできる。
<template>
<div class="parent">
<svg touch-action="none" ref="canv" width="600" height="400" viewBox="0 0 300 300">
<g transform="scale(2,1) rotate(30,0,0) translate(10,50)">
<rect
@pointerdown="onPointerDown"
@pointermove="onPointerMove"
@pointerup="onPointerUp"
:x="p.x"
:y="p.y"
width="100"
height="100"
fill="#DDD"
stroke="black"
></rect>
<rect
v-if="offset"
:x="p.x"
:y="p.y"
:width="offset.x"
:height="offset.y"
fill="#9C9"
style="pointer-events: none"
></rect>
</g>
</svg>
</div>
</template>
<script>
/**
* clientX / Y(スクリーン座標)を element内の座標系に変換する
*/
function screenToSvg(point, el, svg) {
const pt = svg.createSVGPoint();
pt.x = point.x;
pt.y = point.y;
return pt.matrixTransform(el.getScreenCTM().inverse());
}
export default {
data() {
return {
p: {
x: 100,
y: 100
},
offset: null
};
},
methods: {
onPointerUp(e) {
this.offset = null;
},
onPointerDown(e) {
//Rect内のどこがクリックされたか取得
const rect = e.target;
const bbox = rect.getBBox();
this.offset = screenToSvg(
{ x: e.clientX, y: e.clientY },
rect,
this.$refs.canv
);
this.offset.x -= bbox.x;
this.offset.y -= bbox.y;
//領域外のMouseEventもキャプチャする
rect.setPointerCapture(e.pointerId);
},
onPointerMove(e) {
if (this.offset) {
this.p = screenToSvg(
{ x: e.clientX, y: e.clientY },
e.target,
this.$refs.canv
);
//Rect内の掴んだ位置分原点を移動
this.p.x -= this.offset.x;
this.p.y -= this.offset.y;
}
}
}
};
</script>
<style>
.child {
width: 200px;
height: 200px;
background: green;
transform: rotateY(45deg);
}
</style>
← TODO