##したいこと
- 要素をマウスホイールで拡大縮小可能に
- 拡大縮小はカーソルを中心に
- 要素をドラッグで移動可能に
- 親要素をダブルクリックでデフォの状態に
##コード
###HTML
<div id="parent">
<div id="child">
<img src="http://placehold.jp/300x200.png">
</div>
</div>
###CSS
#parent, #child {
width: 300px;
height: 200px;
}
#parent {
position: relative;
overflow: hidden;
}
#child {
cursor: move;
}
###JavaScript
window.onload = () => {
let child = document.getElementById("child");
zoomable(child);
moveable(child);
resettable(child);
}
let zoomable = (element, options) => {
let {minScale, maxScale, magnification} = {
minScale: 0.5,
maxScale: 2,
magnification: -0.0025,
...options
}
let scale = 1;
element.style.transform = "scale(1)";
let zoomElement = (event) => {
event.preventDefault(); // Do not scroll the page
const w1 = element.clientWidth;
const h1 = element.clientHeight;
// currently scale
let s1 = Number(/(?<=\().*?(?=\))/.exec(element.style.transform));
// gap with the parent element
let gX = Number(/.*?(?=px)/.exec(element.style.left));
let gY = Number(/.*?(?=px)/.exec(element.style.top));
// center coordinate of the image
let cX = w1/2 + gX;
let cY = h1/2 + gY;
// coordinate of the pointer
let pX = event.pageX - element.parentElement.offsetLeft;
let pY = event.pageY - element.parentElement.offsetTop;
// distance between the center and the pointer
let dX = pX - cX;
let dY = pY - cY;
// scale the image and move to correct coordinate
scale += event.deltaY * magnification;
scale = Math.min(Math.max(minScale, scale), maxScale);
element.style.left = gX + dX * (1 - scale/s1) + "px";
element.style.top = gY + dY * (1 - scale/s1) + "px";
element.style.transform = `scale(${scale})`;
};
element.addEventListener("wheel", zoomElement);
}
let moveable = (element) => {
element.style.position = "absolute";
let x1;
let y1;
let px1;
let py1;
let moveElement = (event) => {
// Do not drag the image
event.preventDefault();
element.ondragstart = false;
element.style.left = x1 + event.pageX - px1 + "px";
element.style.top = y1 + event.pageY - py1 + "px";
};
element.addEventListener("mousedown", (event) => {
x1 = Number(/.*?(?=px)/.exec(element.style.left));
y1 = Number(/.*?(?=px)/.exec(element.style.top));
px1 = event.pageX;
py1 = event.pageY;
element.addEventListener("mousemove", moveElement);
});
element.addEventListener("mouseup", () => {
element.removeEventListener("mousemove", moveElement);
});
}
let resettable = (element) => {
let resetElement = (event) => {
event.preventDefault();
element.style.transform = "scale(1)";
element.style.left = 0;
element.style.top = 0;
};
element.parentElement.addEventListener("dblclick", resetElement);
}
ポイント
- addEventListener
- 第2引数に無名関数を登録するとremoveEventListenerできないため、関数オブジェクトを変数に代入して使う
- 第2引数の関数に引数を設定するとremoveEventListenerできないため、関数に渡したい値はaddEventListenerの1つ上のブロックで定義してまるごと関数にしてしまう
- Dom操作
- element.style.propertyはDOM要素のstyle属性なので、CSS(style要素)を参照できない
- そのため依存関係にあるプロパティはstyle属性にHTMLかJSで直接記述する必要がある
- その他
- ユーザー操作を可能にするにはデフォの操作を消す必要がある
- element.style.propertyから数値を取得するときに単位消して型変換にする
カーソルを中心に拡大縮小
くっそわかりずらいしどうせ忘れるのでメモ。
- transform: scale()は画像の上下左右中央を中心に拡大縮小される
- left, top(position: absolute)は要素が等倍のときの親要素との差
- 肝のとこは画像の中央の座標とカーソルの座標の拡大前後の差と最初の親要素との差を足してtop, leftに設定してる
- どういうことかっていうとイメージとしてはまずカーソルの位置に画像の中央を移動させて拡大。その後、拡大前にカーソルがあった部分をもう一度カーソルの位置に移動させているといった感じ。
- 一次元ならカーソルを原点から1の位置として画像の中央は2、これを-1だけ(画像の中央をカーソルの位置に)動かして(例えば)2倍に拡大すると、当然もともとカーソルがあった画像内の点は0(=1-1)から-1=(1-(1-0)*2)。-1動かして2倍かけてるから+2だけ動かせば画像の点はカーソルの位置でそこだけ同じになる。