<script src="https://unpkg.com/konva@9/konva.min.js"></script>
<p><span id="scaleTbl"></span><span id="scaleMsg"></span></p>
<p>
<span id='info'>Stage pos...</span>
</p>
<div id='scroller' class='containersize'>
<div id="container" class='container containersize'></div>
<div id='sizer' class=''>
</div>
</div>
<svg style='display: none' id="stars" viewBox="0 0 900 600" width="900" height="600" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<rect x="0" y="0" width="900" height="600" fill="#001829"></rect>
<g fill="#297EA6">
<path
d="M0 -43.4L9.7 -13.4L41.3 -13.4L15.8 5.1L25.5 35.1L0 16.6L-25.5 35.1L-15.8 5.1L-41.3 -13.4L-9.7 -13.4Z"
transform="translate(817 357)"></path>
<path d="M0 -19.8L4.4 -6.1L18.9 -6.1L7.2 2.3L11.7 16L0 7.6L-11.7 16L-7.2 2.3L-18.9 -6.1L-4.4 -6.1Z"
transform="translate(633 473)"></path>
<path
d="M0 -40.6L9.1 -12.5L38.6 -12.5L14.7 4.8L23.9 32.8L0 15.5L-23.9 32.8L-14.7 4.8L-38.6 -12.5L-9.1 -12.5Z"
transform="translate(605 31)"></path>
<path d="M0 -29.3L6.6 -9L27.8 -9L10.6 3.5L17.2 23.7L0 11.2L-17.2 23.7L-10.6 3.5L-27.8 -9L-6.6 -9Z"
transform="translate(599 569)"></path>
<path d="M0 -27.4L6.1 -8.5L26 -8.5L9.9 3.2L16.1 22.1L0 10.5L-16.1 22.1L-9.9 3.2L-26 -8.5L-6.1 -8.5Z"
transform="translate(120 56)"></path>
<path d="M0 -20.8L4.7 -6.4L19.7 -6.4L7.5 2.5L12.2 16.8L0 7.9L-12.2 16.8L-7.5 2.5L-19.7 -6.4L-4.7 -6.4Z"
transform="translate(309 323)"></path>
<path
d="M0 -34.9L7.8 -10.8L33.2 -10.8L12.7 4.1L20.5 28.3L0 13.3L-20.5 28.3L-12.7 4.1L-33.2 -10.8L-7.8 -10.8Z"
transform="translate(25 173)"></path>
<path d="M0 -21.7L4.9 -6.7L20.6 -6.7L7.9 2.6L12.8 17.6L0 8.3L-12.8 17.6L-7.9 2.6L-20.6 -6.7L-4.9 -6.7Z"
transform="translate(241 87)"></path>
<path
d="M0 -40.6L9.1 -12.5L38.6 -12.5L14.7 4.8L23.9 32.8L0 15.5L-23.9 32.8L-14.7 4.8L-38.6 -12.5L-9.1 -12.5Z"
transform="translate(471 183)"></path>
<path d="M0 -18.9L4.2 -5.8L18 -5.8L6.9 2.2L11.1 15.3L0 7.2L-11.1 15.3L-6.9 2.2L-18 -5.8L-4.2 -5.8Z"
transform="translate(866 245)"></path>
<path d="M0 -38.7L8.7 -12L36.8 -12L14.1 4.6L22.7 31.3L0 14.8L-22.7 31.3L-14.1 4.6L-36.8 -12L-8.7 -12Z"
transform="translate(775 581)"></path>
<path d="M0 -31.1L7 -9.6L29.6 -9.6L11.3 3.7L18.3 25.2L0 11.9L-18.3 25.2L-11.3 3.7L-29.6 -9.6L-7 -9.6Z"
transform="translate(262 491)"></path>
<path
d="M0 -40.6L9.1 -12.5L38.6 -12.5L14.7 4.8L23.9 32.8L0 15.5L-23.9 32.8L-14.7 4.8L-38.6 -12.5L-9.1 -12.5Z"
transform="translate(465 574)"></path>
<path d="M0 -24.5L5.5 -7.6L23.3 -7.6L8.9 2.9L14.4 19.9L0 9.4L-14.4 19.9L-8.9 2.9L-23.3 -7.6L-5.5 -7.6Z"
transform="translate(56 414)"></path>
<path
d="M0 -34.9L7.8 -10.8L33.2 -10.8L12.7 4.1L20.5 28.3L0 13.3L-20.5 28.3L-12.7 4.1L-33.2 -10.8L-7.8 -10.8Z"
transform="translate(763 208)"></path>
<path
d="M0 -40.6L9.1 -12.5L38.6 -12.5L14.7 4.8L23.9 32.8L0 15.5L-23.9 32.8L-14.7 4.8L-38.6 -12.5L-9.1 -12.5Z"
transform="translate(389 135)"></path>
<path
d="M0 -34.9L7.8 -10.8L33.2 -10.8L12.7 4.1L20.5 28.3L0 13.3L-20.5 28.3L-12.7 4.1L-33.2 -10.8L-7.8 -10.8Z"
transform="translate(671 150)"></path>
<path
d="M0 -39.6L8.9 -12.2L37.7 -12.2L14.4 4.7L23.3 32.1L0 15.1L-23.3 32.1L-14.4 4.7L-37.7 -12.2L-8.9 -12.2Z"
transform="translate(370 460)"></path>
<path d="M0 -38.7L8.7 -12L36.8 -12L14.1 4.6L22.7 31.3L0 14.8L-22.7 31.3L-14.1 4.6L-36.8 -12L-8.7 -12Z"
transform="translate(523 74)"></path>
<path d="M0 -35.9L8.1 -11.1L34.1 -11.1L13 4.2L21.1 29L0 13.7L-21.1 29L-13 4.2L-34.1 -11.1L-8.1 -11.1Z"
transform="translate(33 550)"></path>
<path
d="M0 -34.9L7.8 -10.8L33.2 -10.8L12.7 4.1L20.5 28.3L0 13.3L-20.5 28.3L-12.7 4.1L-33.2 -10.8L-7.8 -10.8Z"
transform="translate(525 274)"></path>
<path d="M0 -29.3L6.6 -9L27.8 -9L10.6 3.5L17.2 23.7L0 11.2L-17.2 23.7L-10.6 3.5L-27.8 -9L-6.6 -9Z"
transform="translate(56 318)"></path>
<path d="M0 -31.1L7 -9.6L29.6 -9.6L11.3 3.7L18.3 25.2L0 11.9L-18.3 25.2L-11.3 3.7L-29.6 -9.6L-7 -9.6Z"
transform="translate(876 97)"></path>
<path d="M0 -21.7L4.9 -6.7L20.6 -6.7L7.9 2.6L12.8 17.6L0 8.3L-12.8 17.6L-7.9 2.6L-20.6 -6.7L-4.9 -6.7Z"
transform="translate(198 248)"></path>
<path d="M0 -38.7L8.7 -12L36.8 -12L14.1 4.6L22.7 31.3L0 14.8L-22.7 31.3L-14.1 4.6L-36.8 -12L-8.7 -12Z"
transform="translate(450 335)"></path>
<path d="M0 -33L7.4 -10.2L31.4 -10.2L12 3.9L19.4 26.7L0 12.6L-19.4 26.7L-12 3.9L-31.4 -10.2L-7.4 -10.2Z"
transform="translate(651 322)"></path>
<path d="M0 -33L7.4 -10.2L31.4 -10.2L12 3.9L19.4 26.7L0 12.6L-19.4 26.7L-12 3.9L-31.4 -10.2L-7.4 -10.2Z"
transform="translate(744 433)"></path>
<path d="M0 -22.7L5.1 -7L21.5 -7L8.2 2.7L13.3 18.3L0 8.7L-13.3 18.3L-8.2 2.7L-21.5 -7L-5.1 -7Z"
transform="translate(151 461)"></path>
<path d="M0 -18.9L4.2 -5.8L18 -5.8L6.9 2.2L11.1 15.3L0 7.2L-11.1 15.3L-6.9 2.2L-18 -5.8L-4.2 -5.8Z"
transform="translate(485 446)"></path>
<path d="M0 -38.7L8.7 -12L36.8 -12L14.1 4.6L22.7 31.3L0 14.8L-22.7 31.3L-14.1 4.6L-36.8 -12L-8.7 -12Z"
transform="translate(310 225)"></path>
<path d="M0 -35.9L8.1 -11.1L34.1 -11.1L13 4.2L21.1 29L0 13.7L-21.1 29L-13 4.2L-34.1 -11.1L-8.1 -11.1Z"
transform="translate(849 9)"></path>
<path d="M0 -32.1L7.2 -9.9L30.5 -9.9L11.7 3.8L18.9 26L0 12.3L-18.9 26L-11.7 3.8L-30.5 -9.9L-7.2 -9.9Z"
transform="translate(889 574)"></path>
<path d="M0 -32.1L7.2 -9.9L30.5 -9.9L11.7 3.8L18.9 26L0 12.3L-18.9 26L-11.7 3.8L-30.5 -9.9L-7.2 -9.9Z"
transform="translate(145 148)"></path>
<path
d="M0 -34.9L7.8 -10.8L33.2 -10.8L12.7 4.1L20.5 28.3L0 13.3L-20.5 28.3L-12.7 4.1L-33.2 -10.8L-7.8 -10.8Z"
transform="translate(871 475)"></path>
<path d="M0 -29.3L6.6 -9L27.8 -9L10.6 3.5L17.2 23.7L0 11.2L-17.2 23.7L-10.6 3.5L-27.8 -9L-6.6 -9Z"
transform="translate(366 557)"></path>
<path
d="M0 -41.5L9.3 -12.8L39.5 -12.8L15.1 4.9L24.4 33.6L0 15.9L-24.4 33.6L-15.1 4.9L-39.5 -12.8L-9.3 -12.8Z"
transform="translate(229 389)"></path>
<path d="M0 -25.5L5.7 -7.9L24.2 -7.9L9.3 3L15 20.6L0 9.7L-15 20.6L-9.3 3L-24.2 -7.9L-5.7 -7.9Z"
transform="translate(319 34)"></path>
<path d="M0 -27.4L6.1 -8.5L26 -8.5L9.9 3.2L16.1 22.1L0 10.5L-16.1 22.1L-9.9 3.2L-26 -8.5L-6.1 -8.5Z"
transform="translate(24 17)"></path>
<path d="M0 -28.3L6.4 -8.7L26.9 -8.7L10.3 3.3L16.6 22.9L0 10.8L-16.6 22.9L-10.3 3.3L-26.9 -8.7L-6.4 -8.7Z"
transform="translate(541 373)"></path>
<path d="M0 -24.5L5.5 -7.6L23.3 -7.6L8.9 2.9L14.4 19.9L0 9.4L-14.4 19.9L-8.9 2.9L-23.3 -7.6L-5.5 -7.6Z"
transform="translate(221 599)"></path>
<path d="M0 -28.3L6.4 -8.7L26.9 -8.7L10.3 3.3L16.6 22.9L0 10.8L-16.6 22.9L-10.3 3.3L-26.9 -8.7L-6.4 -8.7Z"
transform="translate(574 193)"></path>
<path d="M0 -34L7.6 -10.5L32.3 -10.5L12.3 4L20 27.5L0 13L-20 27.5L-12.3 4L-32.3 -10.5L-7.6 -10.5Z"
transform="translate(707 40)"></path>
<path
d="M0 -41.5L9.3 -12.8L39.5 -12.8L15.1 4.9L24.4 33.6L0 15.9L-24.4 33.6L-15.1 4.9L-39.5 -12.8L-9.3 -12.8Z"
transform="translate(777 113)"></path>
<path d="M0 -27.4L6.1 -8.5L26 -8.5L9.9 3.2L16.1 22.1L0 10.5L-16.1 22.1L-9.9 3.2L-26 -8.5L-6.1 -8.5Z"
transform="translate(446 10)"></path>
<path d="M0 -25.5L5.7 -7.9L24.2 -7.9L9.3 3L15 20.6L0 9.7L-15 20.6L-9.3 3L-24.2 -7.9L-5.7 -7.9Z"
transform="translate(144 352)"></path>
<path d="M0 -32.1L7.2 -9.9L30.5 -9.9L11.7 3.8L18.9 26L0 12.3L-18.9 26L-11.7 3.8L-30.5 -9.9L-7.2 -9.9Z"
transform="translate(135 560)"></path>
<path d="M0 -28.3L6.4 -8.7L26.9 -8.7L10.3 3.3L16.6 22.9L0 10.8L-16.6 22.9L-10.3 3.3L-26.9 -8.7L-6.4 -8.7Z"
transform="translate(199 2)"></path>
<path d="M0 -28.3L6.4 -8.7L26.9 -8.7L10.3 3.3L16.6 22.9L0 10.8L-16.6 22.9L-10.3 3.3L-26.9 -8.7L-6.4 -8.7Z"
transform="translate(403 253)"></path>
</g>
</svg>
body {
margin: 10px;
overflow: hidden;
background-color: #f0f0f0;
}
#container {
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
z-index: 2;
}
#containersize {
box-sizing: border-box;
width: 800px;
height: 600px;
}
#scroller {
box-sizing: border-box;
position: relative;
max-width: 800px;
max-height: 600px;
background-color: lime;
overflow: auto
}
#sizer{
box-sizing: border-box;
position: relative;
min-width: 800px;
min-height: 600px;
background-color: red;
z-index: 1;
}
p {
margin: 4px;
}
#container {
margin: 10px;
}
#scaleMsg {
color: red;
}
table {
display: inline-block;
}
td {
border: 1px solid silver;
min-width: 35;
}
.selected {
background-color: cyan;
}
#zoomErr {
color: red;
background-color: gold;
display: none;
}
$( document ).ready(function(){
console.log('start')
let
scales = [5,4,3,2.5,2,1.5,1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.05],
currentScaleIdx = 6; // holds array index of current scale.
const
size = { width: 800, height: 600}
stage = new Konva.Stage({
container: "container",
width: size.width,
height: size.height,
draggable: true,
dragBoundFunc(pos){
// limit the right & down direction of the stage to keep it in place.
const stageSz = stage.getClientRect()
minX = size.width - stageSz.width
minY = size.height - stageSz.height
if (pos.y < minY){
pos.y = minY
}
else if (pos.y > 0)
{
pos.y = 0
}
if (pos.x < minX){
pos.x = minX
}
else if (pos.x > 0)
{
pos.x = 0
}
return pos
}
}),
rect = new Konva.Rect({
// stroke: 'magenta',
// strokeWidth: 4,
// fill: 'transparent',
width: 100,
height: 100
}),
layer = new Konva.Layer({
draggable: false
});
stage.add(layer);
function setSizer(size){
// set the sizer div to the size of the stage
$('#sizer').css({
x: 0,
y: 0,
width: size.width ,
height: size.height
})
}
function updateScrollers(){
const pos = stage.getClientRect()
setSizer(pos)
$('#info').html("Stage pos " + JSON.stringify(pos));
return
stageScroll = true
$('#scroller').scrollLeft(-pos.x)
$('#scroller').scrollTop(-pos.y)
stageScroll = false
}
// React to user dragging stage
let stageScroll = false;
stage.on("dragmove", function () {
updateScrollers()
});
// React to user scrolling
$('#scroller').on('scroll', function(evt){
// amount we have scrolled
const scrollPos = {
x: -1 * $(this).scrollLeft(),
y: -1 * $(this).scrollTop()
}
// size of sizer
const sizerSize = {
width: $('#sizer').width(),
height:$('#sizer').height()
}
// size of scroller
const scrollerSize = {
width: $('#scroller').width(),
height:$('#scroller').height()
}
// proportion of movement
const prop = {
x: scrollPos.x / (sizerSize.width - scrollerSize.width),
y: scrollPos.y / (sizerSize.height - scrollerSize.height)
}
// react to the vertical scroll
if (prop.y > -1){
stage.y(scrollPos.y)
$('#container').css({
top: $(this).scrollTop()
})
}
else {
// accounts for user throwing mouse to down!
$('#container').css({
top: (sizerSize.height - scrollerSize.height)
})
}
// react to the horizontal scroll
if (prop.x > -1){
stage.x(scrollPos.x)
$('#container').css({
left: $(this).scrollLeft(),
})
}
else {
// accounts for user throwing mouse to right!
$('#container').css({
left: (sizerSize.width - scrollerSize.width)
})
}
})
function init(){
$('#container').css({
left: $('#scroller').scrollLeft(),
top: $('#scroller').scrollTop()
})
// Set up the scale indicator
let tbl = "";
for (let i = 0; i < scales.length; i++){
tbl = tbl + '<td id="scale' + i + '">' + scales[i] + '</td>'
}
tbl = "<table><tr><td>Scale</td>" + tbl + "<td id='zoomErr'>No more zooms available</td></tr></table>"
$("#scaleTbl").html(tbl);
}
init();
stage.on('wheel', (e) => {
// stop default scrolling
e.evt.preventDefault();
const
// Note old scale (scale before moving to next scale)
oldScale = scales[currentScaleIdx],
// Get ABSOLUTE pointer position - doesn't include any transforms (such as scale) of the
// stage. Is just a plain position of pointer relative to top-left corner of the canvas.
pointer = stage.getPointerPosition(),
// Pointer.x/y and stage.x/y are not scaled.
// Compute the mouse position at on the stage at current scale. We use this to calculate
// the stage position movement required at the new scale to place this same point on the
// stage under the mouse pointer after the new scale is applied.
mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
// Are we zooming IN or OUT ?
let direction = e.evt.deltaY > 0 ? -1 : 1;
// when we zoom on trackpad, e.evt.ctrlKey is true
// in that case lets revert direction
if (e.evt.ctrlKey) {
direction = -direction;
}
// This is where we decide the next scale.
// Go to the next zoom position on the array of scales.
if (direction > 0){
zoomErr = currentScaleIdx > 0 ? false : true;
currentScaleIdx = currentScaleIdx > 0 ? currentScaleIdx - 1 : currentScaleIdx;
}
else {
zoomErr = currentScaleIdx < scales.length - 1 ? false : true;
currentScaleIdx = currentScaleIdx < scales.length - 1 ? currentScaleIdx + 1 : currentScaleIdx;
}
// Set the scale value
let newScale = scales[currentScaleIdx];
// Apply this scale to the stage.
stage.scale({ x: newScale, y: newScale });
// Compute the new position of the stage so that the same stage point
// is under the mouse pointer.
const newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
// Apply the new position of the stage
stage.position(newPos);
// Update the message.
$('#scaleMsg').html(scaleMsg);
// Update the info display
showInfo();
updateScrollers()
});
// Update the info display
function showInfo(){
const mousePos = stage.getPointerPosition();
if (mousePos){
$('#info').html('Pos: ' + mousePos.x + ', ' + mousePos.y)
}
$('#info2').html('Stage at: ' + Math.round(stage.x()) + ', ' + Math.round(stage.y()));
$('.selected').removeClass('selected');
$('#scale' + currentScaleIdx).addClass('selected');
if (zoomErr){
$('#zoomErr').show();
}
else {
$('#zoomErr').hide();
}
}
showInfo();
const picLoader = new Image();
picLoader.onload = function () {
layer.destroyChildren();
const pic = new Konva.Image({
x: 0,
y: 0,
width: stage.width() * stage.scaleX(), // cover the stage
height: stage.height() * stage.scaleY(),
opacity: 1
});
layer.add(pic);
pic.image(picLoader);
// layer.add(rect)
// rect.position({
// x: pic.position().x - 2,
// y: pic.position().y - 2,
// })
// rect.size ({
// width: pic.size().width - 4,
// height: pic.size().height - 4,
// })
setSizer(stage.getClientRect())
};
// Just loading something to see in the stage - hapens to be an SVG image in this case
// function to load the svg element with the given id.
function loadSvgPic(picId) {
var svgElement = document.getElementById(picId);
let { width, height } = svgElement.getBBox();
let clonedSvgElement = svgElement.cloneNode(true); // true for deep clone
let outerHTML = clonedSvgElement.outerHTML,
blob = new Blob([outerHTML], { type: "image/svg+xml;charset=utf-8" });
let URL = window.URL || window.webkitURL || window;
let blobURL = URL.createObjectURL(blob);
picLoader.src = blobURL;
}
loadSvgPic('stars')
})
This Pen doesn't use any external CSS resources.