π ΠΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈ ΠΏΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π² 69 ΡΡΡΠΎΡΠΊΠ°Ρ JavaScript
Π§ΡΠΎΠ±Ρ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΡΠΆΠ΅Π»ΠΎΠ²Π΅ΡΠ½ΡΠ΅ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ, ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΏΠΈΡΠ°ΡΡ Π½Π° ΡΠΈΡΡΠΎΠΌ JavaScript ΠΏΡΠΎΡΡΠΎΠ΅ ΠΈ ΡΠ°ΡΡΠΈΡΡΠ΅ΠΌΠΎΠ΅ ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ Π΄Π»Ρ ΠΌΠ°Π½ΠΈΠΏΡΠ»ΡΡΠΈΠΉ Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°ΠΌΠΈ Π²Π΅Π±-ΡΡΡΠ°Π½ΠΈΡΡ.
ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°ΡΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΠΈ Π΄Π΅ΡΠ°Π»ΡΠ½ΠΎ ΡΠ°ΡΡΠΌΠ°ΡΡΠΈΠ²Π°ΡΡ ΠΈΡ β ΡΡΠΎ ΠΎΡΠ΅Π½Ρ ΠΊΡΡΡΠΎΠΉ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΠΉ ΠΎΠΏΡΡ. Π‘ΡΡΠ΅ΡΡΠ²ΡΠ΅Ρ ΠΌΠ½ΠΎΠΆΠ΅ΡΡΠ²ΠΎ Π³ΠΎΡΠΎΠ²ΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊ Ρ ΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠΉ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡΡ, Π½ΠΎ ΡΠ΅Π³ΠΎΠ΄Π½Ρ ΠΌΡ Π½Π°ΠΏΠΈΡΠ΅ΠΌ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΠΉ Π²Π΅Π»ΠΎΡΠΈΠΏΠ΅Π΄ Π½Π° ΡΠΈΡΡΠΎΠΌ JavaScript! ΠΠ°ΡΠ΅ΠΌ?
- Π‘ΡΠΎΡΠΎΠ½Π½ΠΈΠ΅ ΡΠ΅ΡΠ΅Π½ΠΈΡ ΡΠ°ΡΡΠΎ ΠΏΡΠ΅Π΄Π»Π°Π³Π°ΡΡ ΠΈΠ·Π±ΡΡΠΎΡΠ½ΡΡ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡ, ΠΊΠΎΡΠΎΡΠ°Ρ Π²Π°ΠΌ Π½Π΅ Π½ΡΠΆΠ½Π°, Π½ΠΎ Π±Π°Π½Π΄Π» ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΡΠ²Π΅Π»ΠΈΡΠΈΠ²Π°Π΅Ρ.
- Π ΡΠΎΠΌΡ ΠΆΠ΅ ΡΡΠΎ Π·Π°ΠΌΠ΅ΡΠ°ΡΠ΅Π»ΡΠ½ΡΠΉ ΡΠ΅Π»Π»Π΅Π½Π΄ΠΆ, ΠΊΠΎΡΠΎΡΡΠΉ ΡΠ°ΡΡΠ΅Π²Π΅Π»ΠΈΡ Π²Π°Ρ ΠΌΠΎΠ·Π³ ΠΈ ΠΏΡΠΎΠΊΠ°ΡΠ°Π΅Ρ Π½Π°Π²ΡΠΊΠΈ ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΡ.
Π ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ΅ ΠΌΡ ΠΏΠΎΠ»ΡΡΠΈΠΌ ΠΎΡΠ΅Π½Ρ ΠΌΠ°Π»Π΅Π½ΡΠΊΡΡ (Π²ΡΠ΅Π³ΠΎ 69 ΡΡΡΠΎΡΠ΅ΠΊ ΠΊΠΎΠ΄Π°!), ΠΏΡΠΎΡΡΡΡ ΠΈ ΡΠ΄ΠΎΠ±Π½ΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΡΠΊΡ Π΄Π»Ρ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈ ΠΏΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΡ.
Π Π°Π·ΠΌΠ΅ΡΠΊΠ° ΠΈ ΡΡΠΈΠ»ΠΈ
Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ HTML-ΡΡΡΠ°Π½ΠΈΡΡ ΠΈ ΡΠ°Π·ΠΌΠ΅ΡΡΠΈΠΌ Π½Π° Π½Π΅ΠΉ ΡΠ»Π΅ΠΌΠ΅Π½Ρ-ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅Ρ (#container). ΠΠ½ΡΡΡΡ ΠΏΠΎΠΌΠ΅ΡΡΠΈΠΌ ΡΠ°Π±ΠΎΡΡΡ ΠΎΠ±Π»Π°ΡΡΡ (.area), ΠΊΠΎΡΠΎΡΡΡ ΠΌΡ ΠΈ Π±ΡΠ΄Π΅ΠΌ Π½Π΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°ΡΡ ΠΈ ΠΏΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°ΡΡ.
<div id="container">
<div class="area">
<div class="rectangle"></div>
<div class="circle"></div>
<div class="text-area">
<h1>Example line of text</p>
</div>
</div>
</div>
ΠΠ½ΡΡΡΠΈ ΡΠ°Π±ΠΎΡΠ΅ΠΉ ΠΎΠ±Π»Π°ΡΡΠΈ Π½Π°Ρ ΠΎΠ΄ΡΡΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ², ΠΊΠΎΡΠΎΡΡΠ΅ Π½Π΅ Π½Π΅ΡΡΡ Π½ΠΈΠΊΠ°ΠΊΠΎΠΉ ΡΠΌΡΡΠ»ΠΎΠ²ΠΎΠΉ Π½Π°Π³ΡΡΠ·ΠΊΠΈ, Π° ΠΏΡΠΎΡΡΠΎ ΠΏΡΠ΅Π΄Π½Π°Π·Π½Π°ΡΠ΅Π½Ρ Π΄Π»Ρ Π΄Π΅ΠΌΠΎΠ½ΡΡΡΠ°ΡΠΈΠΈ ΡΠ°Π±ΠΎΡΡ ΠΊΠΎΠ΄Π°.
ΠΠΎΠ±Π°Π²ΠΈΠΌ ΡΠ°ΠΊΠΆΠ΅ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΡΡΠΈΠ»Π΅ΠΉ Π΄Π»Ρ ΠΎΡΠΎΡΠΌΠ»Π΅Π½ΠΈΡ ΡΡΡΠ°Π½ΠΈΡΡ:
body {
overflow: hidden;
}
#container {
height: 100%;
width: 100%;
position: absolute;
}
.area {
border: 1px dashed black;
height: 80%;
width: 80%;
position: absolute;
}
.circle {
height: 200px;
width: 200px;
background-color: navajowhite;
border-radius: 50%;
display: inline-block;
position: relative;
}
.rectangle {
background-color: navajowhite;
height: 150px;
width: 250px;
position: relative;
}
.text-area {
float: right;
position: relative;
}
ΠΠ»Ρ body ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ overflow: hidden. ΠΡΠΎ Π½ΡΠΆΠ½ΠΎ, ΡΡΠΎΠ±Ρ ΠΈΠ·Π±Π΅ΠΆΠ°ΡΡ ΠΏΠ΅ΡΠ΅ΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΡΡΡΠ°Π½ΠΈΡΡ ΠΈ ΠΏΠΎΡΠ²Π»Π΅Π½ΠΈΡ ΠΏΡΠΎΠΊΡΡΡΠΊΠΈ ΠΏΡΠΈ ΡΡΠ΅Π·ΠΌΠ΅ΡΠ½ΠΎΠΌ ΡΠ²Π΅Π»ΠΈΡΠ΅Π½ΠΈΠΈ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°.
Π’Π°ΠΊΠΆΠ΅ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΡΠ°ΠΌΠΊΡ Π΄Π»Ρ Π²ΠΈΠ·ΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠ±ΠΎΠ·Π½Π°ΡΠ΅Π½ΠΈΡ ΡΠ°Π±ΠΎΡΠ΅ΠΉ ΠΎΠ±Π»Π°ΡΡΠΈ (.area) ΠΈ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΎΠ±Π»Π°Π³ΠΎΡΠΎΠ΄ΠΈΠΌ Π΄Π΅ΠΌΠΎ-ΠΊΠΎΠ½ΡΠ΅Π½Ρ (ΠΊΠ»Π°ΡΡΡ .circle, .rectangle ΠΈ .text-area).
Π‘ΠΊΡΠΈΠΏΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ
ΠΠΎΠ΄ ΡΠ°ΠΌΠΎΠΉ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ Π±ΡΠ΄Π΅Ρ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°ΡΡΡΡ Π² ΡΠ°ΠΉΠ»Π΅ renderer.js. ΠΠΊΡΠΏΠΎΡΡΠΈΡΡΠ΅ΠΌ ΠΈΠ· ΠΌΠΎΠ΄ΡΠ»Ρ Π³Π»Π°Π²Π½ΡΡ ΡΡΠ½ΠΊΡΠΈΡ renderer:
const renderer = ({ minScale, maxScale, element, scaleSensitivity = 10 }) => {
const state = {
element,
minScale,
maxScale,
scaleSensitivity,
transformation: {
originX: 0,
originY: 0,
translateX: 0,
translateY: 0,
scale: 1
},
};
return Object.assign({}, makeZoom(state), makePan(state));
};
module.exports = { renderer };
ΠΠ½Π° ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ Π±Π°Π·ΠΎΠ²ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ:
minScaleβ ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ ΠΌΠ°ΡΡΡΠ°Π±;maxScaleβ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ ΠΌΠ°ΡΡΡΠ°Π±;elementβ DOM-ΡΠ»Π΅ΠΌΠ΅Π½Ρ, Ρ ΠΊΠΎΡΠΎΡΡΠΌ Π±ΡΠ΄ΡΡ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΡΡΡ ΠΌΠ°Π½ΠΈΠΏΡΠ»ΡΡΠΈΠΈ;scaleSensitivityβ ΠΊΠΎΡΡΡΠΈΡΠΈΠ΅Π½Ρ ΡΡΠ²ΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΡ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ, ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ 10.
Π Π·Π°ΠΌΡΠΊΠ°Π½ΠΈΠΈ ΡΡΠ½ΠΊΡΠΈΠΈ ΡΠΎΠ·Π΄Π°Π΅ΡΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ β state, ΠΊΠΎΡΠΎΡΡΠΉ Ρ
ΡΠ°Π½ΠΈΡ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ ΠΈ ΡΠΎΠ²Π΅ΡΡΠ΅Π½Π½ΡΠ΅ Π½Π°Π΄ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠΌ ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΡ (ΠΏΠΎΠ»Π΅ transformation).
ΠΠ· ΡΡΠ½ΠΊΡΠΈΠΈ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΡΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ Ρ Π½Π°Π±ΠΎΡΠΎΠΌ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ². ΠΡΠΈ ΡΡΠΎΠΌ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈ ΠΏΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ°Π·Π΄Π΅Π»Π΅Π½Ρ Π½Π° ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ-ΠΊΠΎΠ½ΡΡΡΡΠΊΡΠΎΡΡ β makeZoom ΠΈ makePan, ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΡΠ°Π·Π±Π΅ΡΠ΅ΠΌ ΡΡΡΡ ΠΏΠΎΠ·ΠΆΠ΅. ΠΠΎΠ½ΡΡΡΡΠΊΡΠΎΡΡ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΎΠ±ΡΠΈΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡ ΠΈ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°ΡΡ ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ Π½Π°Π±ΠΎΡ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠ² Π΄Π»Ρ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΡ Ρ Π½ΠΈΠΌ.
Π’Π°ΠΊΠΎΠΉ ΠΏΠΎΠ΄Ρ ΠΎΠ΄ Π½Π°Π·ΡΠ²Π°Π΅ΡΡΡ ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡΠΈΠ΅ΠΉ ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΏΡΠΎΡΠ΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ Π½ΠΎΠ²ΡΡ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡ ΠΈ Π»Π΅Π³ΡΠ΅ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅.
Π’ΡΠ°Π½ΡΡΠΎΡΠΌΠ°ΡΠΈΠΈ
ΠΡΠ΅ ΠΌΠ°Π½ΠΈΠΏΡΠ»ΡΡΠΈΠΈ Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠΌ Π±ΡΠ΄ΡΡ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΡΡΡ ΡΠ΅ΡΠ΅Π· ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ ΡΠ²ΠΎΠΉΡΡΠ²Π° transform. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ CSS-ΡΡΠ½ΠΊΡΠΈΡ matrix, ΠΊΠΎΡΠΎΡΠΎΠΉ Π½ΡΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΡ ΠΏΡΠ°Π²ΠΈΠ»ΡΠ½ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΠΌΠ°ΡΡΡΠ°Π±Π° (scale) ΠΈ ΡΠ΄Π²ΠΈΠ³Π° (translateX ΠΈ translateY):
const getMatrix = ({ scale, translateX, translateY }) =>
`matrix(${scale}, 0, 0, ${scale}, ${translateX}, ${translateY})`;
ΠΡΠΏΠΎΠΌΠΎΠ³Π°ΡΠ΅Π»ΡΠ½Π°Ρ ΡΡΠ½ΠΊΡΠΈΡ getMatrix ΠΏΡΠΎΡΡΠΎ ΡΠΎΡΠΌΠΈΡΡΠ΅Ρ ΡΠ°Π±Π»ΠΎΠ½Π½ΡΡ ΡΡΡΠΎΠΊΡ ΠΏΡΠ°Π²ΠΈΠ»ΡΠ½ΠΎΠ³ΠΎ ΡΠΎΡΠΌΠ°ΡΠ°, ΠΊΠΎΡΠΎΡΡΡ Π½ΡΠΆΠ½ΠΎ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π² ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ style.transform ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°.
ΠΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
ΠΡΠΈ ΠΏΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΠΈ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΈΠ·ΠΌΠ΅Π½ΡΡΡΡΡ ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° Π½Π° ΡΡΡΠ°Π½ΠΈΡΠ΅, ΡΠΎ Π΅ΡΡΡ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΡΡΡ Π΅Π³ΠΎ ΡΠ΄Π²ΠΈΠ³. Π€ΡΠ½ΠΊΡΠΈΡ pan ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅Ρ ΡΠ΅ΠΊΡΡΠ΅Π΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° (state), Π° ΡΠ°ΠΊΠΆΠ΅ Π½ΠΎΠ²ΡΠ΅ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΡ. ΠΠ°ΡΠ΅ΠΌ ΠΎΠ½Π° ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅, ΠΏΡΠΈΠ±Π°Π²Π»ΡΡ Π½ΠΎΠ²ΡΠΉ ΡΠ΄Π²ΠΈΠ³ ΠΊ ΡΠ΅ΠΊΡΡΠ΅ΠΌΡ ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΡ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ style ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°.
const pan = ({ state, originX, originY }) => {
state.transformation.translateX += originX;
state.transformation.translateY += originY;
state.element.style.transform =
getMatrix({ scale: state.transformation.scale, translateX: state.transformation.translateX, translateY: state.transformation.translateY });
};
Π’Π΅ΠΏΠ΅ΡΡ ΡΠ΅Π°Π»ΠΈΠ·ΡΠ΅ΠΌ Π΄Π²Π° ΠΌΠ΅ΡΠΎΠ΄Π°:
panByβ ΠΏΡΠΎΡΡΠΎΠΉ ΡΠ΄Π²ΠΈΠ³ Π½Π° ΡΠΊΠ°Π·Π°Π½Π½ΡΠ΅ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΡ;panToβ ΡΠ΄Π²ΠΈΠ³ Ρ ΠΎΠ΄Π½ΠΎΠ²ΡΠ΅ΠΌΠ΅Π½Π½ΡΠΌ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ.
const makePan = (state) => ({
panBy: ({ originX, originY }) => pan({ state, originX, originY }),
panTo: ({ originX, originY, scale }) => {
state.transformation.scale = scale;
pan({ state, originX: originX - state.transformation.translateX, originY: originY - state.transformation.translateY });
},
});
ΠΡΠΈ ΡΠ΄Π²ΠΈΠ³Π΅ Ρ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° Π½ΡΠΆΠ½ΠΎ ΡΠΊΠΎΡΡΠ΅ΠΊΡΠΈΡΠΎΠ²Π°ΡΡ.
ΠΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
ΠΠ»Ρ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ ΡΠ°Π·ΠΌΠ΅ΡΠ° ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° Π½Π°ΠΌ ΠΏΠΎΡΡΠ΅Π±ΡΠ΅ΡΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ Π²ΡΠΏΠΎΠΌΠΎΠ³Π°ΡΠ΅Π»ΡΠ½ΡΡ ΡΡΠ½ΠΊΡΠΈΠΉ Π΄Π»Ρ ΡΠ°ΡΡΠ΅ΡΠΎΠ²:
const hasPositionChanged = ({ pos, prevPos }) =>
pos !== prevPos;
const valueInRange = ({ minScale, maxScale, scale }) =>
scale <= maxScale && scale >= minScale;
const getTranslate = ({ minScale, maxScale, scale }) => ({ pos, prevPos, translate }) =>
valueInRange({ minScale, maxScale, scale }) && hasPositionChanged({ pos, prevPos })
? translate + (pos - prevPos * scale) * (1 - 1 / scale)
: translate;
const getScale = ({ scale, minScale, maxScale, scaleSensitivity, deltaScale }) => {
let newScale = scale + (deltaScale / (scaleSensitivity / scale));
newScale = Math.max(minScale, Math.min(newScale, maxScale));
return [scale, newScale];
};
ΠΠ΅ΡΠΎΠ΄ getScale ΡΠ°ΡΡΡΠΈΡΡΠ²Π°Π΅Ρ Π½ΠΎΠ²ΡΠΉ ΠΌΠ°ΡΡΡΠ°Π± Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠ΅Π³ΠΎ Π·Π½Π°ΡΠ΅Π½ΠΈΡ, ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΈ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ³ΠΎ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠΉ (minScale, maxScale) ΠΈ ΠΊΠΎΡΡΡΠΈΡΠΈΠ΅Π½ΡΠΎΠ² (scaleSensitivity, deltaScale).
ΠΠ΅ΡΠΎΠ΄ getTranslate ΡΠ°ΡΡΡΠΈΡΡΠ²Π°Π΅Ρ Π½ΠΎΠ²ΡΠΉ ΡΠ΄Π²ΠΈΠ³ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΠΌΠ°ΡΡΡΠ°Π±Π° ΠΈ ΡΠ΅ΠΊΡΡΠ΅ΠΉ ΠΈ ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠ΅ΠΉ ΠΏΠΎΠ·ΠΈΡΠΈΠΈ.
Π Π²ΠΎΡ ΠΈ ΡΠ΅Π°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΡΠ½ΠΊΡΠΈΠΈ makeZoom:
const makeZoom = (state) => ({
zoom: ({ x, y, deltaScale }) => {
const { left, top } = state.element.getBoundingClientRect();
const { minScale, maxScale, scaleSensitivity } = state;
const [ scale, newScale ] = getScale({ scale: state.transformation.scale, deltaScale, minScale, maxScale, scaleSensitivity });
const originX = x - left;
const originY = y - top;
const newOriginX = originX / scale;
const newOriginY = originY / scale;
const translate = getTranslate({ scale, minScale, maxScale });
const translateX = translate({ pos: originX, prevPos: state.transformation.originX, translate: state.transformation.translateX });
const translateY = translate({ pos: originY, prevPos: state.transformation.originY, translate: state.transformation.translateY });
state.element.style.transformOrigin = `${newOriginX}px ${newOriginY}px`;
state.element.style.transform = getMatrix({ scale: newScale, translateX, translateY });
state.transformation = { originX: newOriginX, originY: newOriginY, translateX, translateY, scale: newScale };
}
});
ΠΠ½Π° Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ ΠΌΠ΅ΡΠΎΠ΄ zoom, ΠΏΡΠ΅Π΄Π½Π°Π·Π½Π°ΡΠ΅Π½Π½ΡΠΉ Π΄Π»Ρ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°. ΠΠ½ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΠΊΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΡ ΠΊΡΡΡΠΎΡΠ°, Π° ΡΠ°ΠΊΠΆΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ deltaScale β ΠΊΠΎΡΡΡΠΈΡΠΈΠ΅Π½Ρ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅Ρ Π½Π°ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ (1 Π΄Π»Ρ ΡΠ²Π΅Π»ΠΈΡΠ΅Π½ΠΈΡ, -1 Π΄Π»Ρ ΡΠΌΠ΅Π½ΡΡΠ΅Π½ΠΈΡ).
Π€ΡΠ½ΠΊΡΠΈΡ Π²ΡΡΠΈΡΠ»ΡΠ΅Ρ Π½ΠΎΠ²ΡΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΡΡΠ°Π½ΡΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅Ρ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ style ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°.
ΠΡΠΈ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΠΈ ΠΊΡΠΎΠΌΠ΅ style.transform Π½ΡΠΆΠ½ΠΎ ΠΈΠ·ΠΌΠ΅Π½ΡΡΡ ΡΠ°ΠΊΠΆΠ΅ ΡΠ²ΠΎΠΉΡΡΠ²ΠΎ style.transformOrigin, ΡΡΠΎΠ±Ρ ΡΠΊΠΎΡΡΠ΅ΠΊΡΠΈΡΠΎΠ²Π°ΡΡ ΠΏΠΎΠ·ΠΈΡΠΈΡ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°. Π ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΡΠΊΡΠΏΠ΅ΡΠΈΠΌΠ΅Π½ΡΠ° Π²Ρ ΠΌΠΎΠΆΠ΅ΡΠ΅ Π·Π°ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠΈΡΠΎΠ²Π°ΡΡ 14 ΡΡΡΠΎΡΠΊΡ ΠΈ ΠΏΠΎΡΠΌΠΎΡΡΠ΅ΡΡ, ΡΡΠΎ Π±ΡΠ΄Π΅Ρ.
ΠΠ»Π°Π²Π½ΡΠΉ ΡΠ°ΠΉΠ»
ΠΡΠΎΠΌΠ΅ ΡΠΎΠ³ΠΎ ΠΌΡ ΡΠ΄Π΅Π»Π°Π΅ΠΌ Π³Π»Π°Π²Π½ΡΠΉ ΡΠ°ΠΉΠ» ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ index.js:
(() => {
const { renderer } = require("./src/renderer");
const container = document.getElementById("container");
const instance = renderer({ minScale: .1, maxScale: 30, element: container.children[0], scaleSensitivity: 50 });
container.addEventListener("wheel", (event) => {
if (!event.ctrlKey) {
return;
}
event.preventDefault();
instance.zoom({
deltaScale: Math.sign(event.deltaY) > 0 ? 1 : -1,
x: event.pageX,
y: event.pageY
});
});
container.addEventListener("dblclick", () => {
instance.panTo({
originX: 0,
originY: 0,
scale: 1,
});
});
container.addEventListener("mousemove", (event) => {
if (!event.shiftKey) {
return;
}
event.preventDefault();
instance.panBy({
originX: event.movementX,
originY: event.movementY
});
})
})();
ΠΠ»ΠΈΠ΅Π½ΡΡΠΊΠΈΠΉ ΠΊΠΎΠ΄ ΡΠΎΠ·Π΄Π°Π΅Ρ ΡΠΊΠ·Π΅ΠΌΠΏΠ»ΡΡ renderer ΠΈ ΠΏΠ΅ΡΠ΅Π΄Π°Π΅Ρ Π΅ΠΌΡ Π±Π°Π·ΠΎΠ²ΡΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΡ:
- ΡΠ»Π΅ΠΌΠ΅Π½Ρ, ΡΠ°Π·ΠΌΠ΅Ρ ΠΊΠΎΡΠΎΡΠΎΠ³ΠΎ Π±ΡΠ΄Π΅Ρ ΠΈΠ·ΠΌΠ΅Π½ΡΡΡΡΡ;
- ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ ΠΈ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΡΠΉ ΠΌΠ°ΡΡΡΠ°Π±;
- ΠΊΠΎΡΡΡΠΈΡΠΈΠ΅Π½Ρ ΡΡΠ²ΡΡΠ²ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ.
ΠΠ°ΡΠ΅ΠΌ ΡΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°ΡΡΡΡ ΡΠ»ΡΡΠ°ΡΠ΅Π»ΠΈ ΡΠΎΠ±ΡΡΠΈΠΉ ΠΌΡΡΠΈ ΠΈ Π² Π½ΡΠΆΠ½ΡΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ Π²ΡΠ·ΡΠ²Π°ΡΡΡΡ Π½ΡΠΆΠ½ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ:
- Π΄Π»Ρ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΠΊΠΎΠ»Π΅ΡΠΈΠΊΠΎ ΠΌΡΡΠΈ ΠΈΠ»ΠΈ ΡΠ΅Π½ΡΠΎΡΠ½ΡΡ ΠΏΠ°Π½Π΅Π»Ρ, Π·Π°ΠΆΠ°Π² ΠΊΠ»Π°Π²ΠΈΡΡ CTRL.
- Π΄Π»Ρ ΠΏΠ΅ΡΠ΅ΠΌΠ΅ΡΠ΅Π½ΠΈΡ β ΠΏΠ΅ΡΠ΅ΠΌΠ΅ΡΠ°ΠΉΡΠ΅ ΠΌΡΡΡ ΠΈΠ»ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ ΡΠ°ΡΠΏΠ°Π΄, Π·Π°ΠΆΠ°Π² ΠΊΠ»Π°Π²ΠΈΡΡ SHIFT.
- ΠΠ²ΠΎΠΉΠ½ΠΎΠΉ ΡΠ΅Π»ΡΠΎΠΊ ΠΌΡΡΠΈ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²ΠΈΡ ΠΈΡΡ ΠΎΠ΄Π½ΠΎΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΠ΅ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ°.
ΠΠ΅ΠΌΠΎ-ΠΏΡΠΈΠΌΠ΅Ρ:
Π’Π΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅
ΠΡΠΎΠ²Π΅ΡΠΈΠΌ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π½ΡΠ΅ ΡΡΠ½ΠΊΡΠΈΠΈ Ρ ΠΏΠΎΠΌΠΎΡΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ Mocha:
const panByTestCases = [
{
description: 'should pan by passed originX and originY (x: 100, y: 100)',
minScale: .1,
maxScale: 20,
origins: [
{
originX: 100,
originY: 100,
}
],
result: 'matrix(1, 0, 0, 1, 100, 100)'
},
{
description: 'should pan by passed originX and originY (x: 50, y: 50)',
minScale: .1,
maxScale: 20,
origins: [
{
originX: 100,
originY: 100,
},
{
originX: -50,
originY: -50,
}
],
result: 'matrix(1, 0, 0, 1, 50, 50)'
},
{
description: 'should pan by passed originX and originY (x: -50, y: 50)',
minScale: .1,
maxScale: 20,
origins: [
{
originX: -100,
originY: 100,
},
{
originX: 50,
originY: -50,
}
],
result: 'matrix(1, 0, 0, 1, -50, 50)'
}
];
module.exports = {
panByTestCases,
};
const assert = require('assert');
const { renderer } = require('../src/renderer');
const { panByTestCases } = require('./renderer.testCases');
describe('renderer', () => {
let _element;
beforeEach(() => {
_element = {
getBoundingClientRect: () => ({ left: 0, top: 0 }),
style: {
transform: "",
transformOrigin: "",
}
}
});
describe('#canPan()', () => {
describe('#panBy()', () => {
panByTestCases.forEach(({ description, minScale, maxScale, origins, result }) =>
it(description, () => {
const instance = renderer({ minScale, maxScale, element: _element })
origins.forEach(({ originX, originY }) => instance.panBy({ originX, originY }))
assert.equal(_element.style.transform, result);
}),
);
});
});
ΠΠ΅ΡΠ΅Π΄ ΠΊΠ°ΠΆΠ΄ΡΠΌ ΡΠ΅ΡΡΠΎΠΌ (beforeEach) ΡΠΎΠ·Π΄Π°Π΅ΡΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ _element Ρ Π΄Π΅ΡΠΎΠ»ΡΠ½ΡΠΌΠΈ Π·Π½Π°ΡΠ΅Π½ΠΈΡΠΌΠΈ.
ΠΠ΅ΠΉΡΡ ΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ Π΄Π»Ρ ΡΠ΄ΠΎΠ±ΡΡΠ²Π° Π²ΡΠ½Π΅ΡΠ΅Π½Ρ Π² ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΠΉ ΡΠ°ΠΉΠ» renderer.testCases.js.
Π ΠΈΡΠΎΠ³Π΅ Ρ Π½Π°Ρ ΠΏΠΎΠ»ΡΡΠΈΠ»ΡΡ ΠΎΡΠ΅Π½Ρ ΠΏΡΠΎΡΡΠΎΠΉ ΠΈ ΡΠ΄ΠΎΠ±Π½ΡΠΉ ΠΈΠ½ΡΡΡΡΠΌΠ΅Π½Ρ Π΄Π»Ρ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΠΈ ΠΏΠ°Π½ΠΎΡΠ°ΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΡ Π½Π° JavaScript, ΡΠΎΡΡΠΎΡΡΠΈΠΉ Π²ΡΠ΅Π³ΠΎ ΠΈΠ· 69 ΡΡΡΠΎΠΊ ΠΊΠΎΠ΄Π°. ΠΠ³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠΊΡΠ°ΡΠΈΡΡ Π±ΠΎΠ»ΡΡΠ΅, Π½ΠΎ Π½Π΅ Ρ ΠΎΡΠ΅ΡΡΡ ΡΠ΅ΡΡΡΡ ΡΠΈΡΠ°Π±Π΅Π»ΡΠ½ΠΎΡΡΡ.
ΠΠΎΠ»Π½ΡΡ ΠΈ ΠΌΠΈΠ½ΠΈΡΠΈΡΠΈΡΠΎΠ²Π°Π½Π½ΡΡ Π²Π΅ΡΡΠΈΠΈ ΠΈΡΠΈΡΠ΅ Π² ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠ°.