svgでzoomとpanを実装する
code:app.js
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
document.documentElement.classList.add('mobile-browser');
}
const getEventPosition = (ev) => {
const x = ev.offsetX;
const y = ev.offsetY;
return { x, y };
};
const scaleFactor = 1.01;
const { html, render, useState, useCallback } = htmPreact;
const App = () => {
const handleWheel = useCallback((e) => {
e.preventDefault();
const position = getEventPosition(e);
console.log(position);
const scale = e.deltaY < 0 ? scaleFactor : 1 / scaleFactor;
// normalized position from 0 to 1
const sx = position.x / e.target.clientWidth;
const sy = position.y / e.target.clientHeight;
setViewBox(
const x = minX + width * sx;
const y = minY + height * sy;
const scaledWidth = width * scale;
const scaledHeight = height * scale;
const scaledMinX = x + scale * (minX - x);
const scaledMinY = y + scale * (minY - y);
},
);
}, []);
const handleDrag = useCallback((e) => {
e.preventDefault();
// マウスが領域外に出ても、ドラッグを継続する
e.target.setPointerCapture(e.pointerId);
// pointerup時に自動で解放される
let prevX = e.clientX;
let prevY = e.clientY;
const handleMove = (e) => {
const dx = e.clientX - prevX;
const dy = e.clientY - prevY;
setViewBox(
[${(parseInt(minX) - dx)}, ${(parseInt(minY) - dy)}, width, height],
);
prevX = e.clientX;
prevY = e.clientY;
};
e.target.addEventListener("pointermove", handleMove);
const handleUp = () => {
e.target.removeEventListener("pointermove", handleMove);
e.target.removeEventListener("pointerup", handleMove);
}
e.target.addEventListener("pointerup", handleUp);
return handleUp;
}, []);
return html`
<div id="description">Wheel mouse to zoom in-out</div>
<svg viewBox=${viewBox.join(" ")} width="100px" height="100px" onWheel=${handleWheel} onPointerDown=${handleDrag}>
<circle cx="0" cy="0" r="20" />
</svg>
<div id="message">${ViewBox: ${viewBox.join(", ")}}</div>
<div class="warning">
Please try on Desktop browsers.
</div>
`;
};
render(html<${App} />, document.body);
code:app.css
html, body {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
font-family: system-ui;
}
svg {
background: white;
}
margin-bottom: 30px;
}
margin-top: 20px;
}
.warning {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 20px;
background: rgba(0,0,0,.7);
color: white;
text-align: center;
font-weight: 600;
font-size: 1.6rem;
line-height: 2;
}
.mobile-browser .warning {
display: block;
}