Scaling and centering SVG with JS - javascript

Learning SVG / snap. With some help I can pull a region from a map and load it onto a modal per the fiddle here.
I am now trying to keep the SVG within a 200px by 200px div, maintaining its aspect ratio, and to scale, so each region in the modal will fit in the above box but remain to scale against other regions.
var m = Snap('#world-map');
var d = Snap('#svg-region');
var regionSVG = m.select('#' + region);
var p = regionSVG.clone();
d.append(p);
var bb = regionSVG.getBBox();
var vb = bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height;
d.attr({
viewBox: vb
})
I tried processing the data in the bbox to create a scale, but failed miserably.
function zoomBBox(bbox)
{
var zbbox, ratio, diffx, diffy;
var length = 200;
if (bbox.width > bbox.height) {
ratio = bbox.width / length;
zbbox.width = length;
zbbox.height = bbox.height * ratio;
} else {
ratio = bbox.height / length;
zbbox.width = bbox.width * ratio;
zbbox.height = length;
}
diffx = (zbbox.width - length) / 2;
diffy = (zbbox.height - length) / 2;
zbbox.x = bbox.x + diffx;
zbbox.y = bbox.y + diffy;
return zbbox;
}

just add
.modal-body svg {
max-width: 100%;
max-height: 100%;
}
https://jsfiddle.net/0djhebes/7/

You just need the power of CSS :D
You need to define in the SVG #svg-region inside the modal via css HEIGHT & WIDTH that you want that they appear at their maximum.
For example 100px & 100px and they will be rendered inside that box
#svg-region{width:100px; height:100px;}
For aligning it, use a as wrapper around #svg-region with the following css (I think that should work, didn't test it to align vertically and horizontally)
.wrapper{display:flex; jusfity-alignment:center; align-item:center;}

Related

Using getComputedStyle with border-box should return height sans border and padding

EDIT 3/Final: Th Computed Style problem/question is explained below but, for the benefit of others coming later, my real problem is solved with Flex Boxes and Vx measurements in conjunction with border-box. IMHO "display: flex;" is the answer to many questions and, although I'm struggling to get it to do what I want, stops you having to work against CSS!
EDIT 2: The following undoubtedly needs refactoring but if you can tell me that it does what I was asking for that'd be great. The change I had to make was to add clientTop in with offsetTop in the equation: -
function resizeContent()
{
var browserHeight = window.outerHeight;
var offesetHeight, offsetWidth;
var viewHeight = window.innerHeight;
var viewWidth = window.innerWidth;
var chromeFootPrint = browserHeight - viewHeight;
var tmpHeight = viewHeight;
if (window.matchMedia("(orientation: portrait)").matches) {
if (viewWidth > viewHeight) {
viewHeight = viewWidth - chromeFootPrint;
viewWidth = tmpHeight + chromeFootPrint;
}
} else {
if (viewWidth < viewHeight) {
viewHeight = viewWidth - chromeFootPrint;
viewWidth = tmpHeight + chromeFootPrint;
}
}
var dimTarget = logScroll;
var offsetTop = dimTarget.offsetTop + dimTarget.clientTop;
var offsetLeft = dimTarget.offsetLeft + dimTarget.clientLeft;
while (dimTarget = dimTarget.offsetParent) {
offsetTop += dimTarget.offsetTop + dimTarget.clientTop;
offsetLeft += dimTarget.offsetLeft + dimTarget.clientLeft;
}
logScrollHeight = viewHeight - (offsetTop + fireBreak);
logScroll.style.height = logScrollHeight + "px";
logScroll.style.width = getStyle(contentDiv).width;
logWindow.style.height = logScroll.style.height;
logWindow.style.width = logScroll.style.width;
logWindow.scrollTop = logWindow.scrollHeight - logScrollHeight;
contentDiv.style.visibility = "visible"; // Now we're presentable
}
and this is the fire-break calculation: -
var outerStyle = getStyle(wholeScreen);
fireBreak = Number(outerStyle.paddingBottom.match("[0-9.]*"))
+ Number(outerStyle.borderBottomWidth.match("[0-9.]*"))
;
resizeContent();
EDIT 1: Ok, let me re-phrase the question: - How to I find out the height of my DIVs content with: -
width: 250px;
border: 3px solid green;
padding: 0.5em;
box-sizing: border-box;
I am currently having to do this: -
logScrollHeight = viewHeight -
(offsetTop + Number(getStyle(headerDiv).paddingBottom.match("[0-9.]*")));
Original question: -
This is bound to be a duplicate but after nearly an hour of looking I have found many similar/identical questions but no real answer :-(
Why aren't the boundryWith and padding deducted from height?
Thankfully the boundryBottomWidth and PaddingBottom return have been converted to pixels (including the "px" string sadly) but doesn't the standard say that the usable height should be returned?
To get the height of an element, you don't use getComputedStyle.
getComputedStyle should only be used to get the parsed values that are currently applied from different style-sheets. In other words, you can see it as a live style-sheet, only targeted to a single element, with standardized units.
But in no way it should be used to get the current height or width of an element. Too many factors may interfere with the set value, and an element can even have an height without having any CSS height rule set.
So yes... when the height CSS rule is set to auto, you will get the computed value, which may coincide with the real height of the element, but it also may not be so.
So in order to get the displayed height of an element, without the border and padding, we will need to do some calculations ourself.
Element#getBoundingClientRect() will give us the real displayed dimensions of our element, transformations included. .offsetHeight will give us the untransformed height including the border-box, and .clientHeight will give us the untransformed height with the padding-box.
This means that we will first have to get all the border and padding computed values, then get the current scale of our element, and finally remove the scaled padding + border boxes from the values we get with getBoundingClientRect.
Here is an example, which will draw a new rectangle div atop the element's bounding-box without padding and border boxes.
let marker;
scale.onclick = e => {
element.classList.toggle('scaled');
drawRect();
}
boxSizing.onclick = e => {
element.classList.toggle('boxSized');
drawRect();
}
function drawRect() {
// remove previous marker if any
if (marker && marker.parentNode) {
marker.remove();
marker = null;
}
// first get the border and padding values
let computed = getComputedStyle(element);
let borderLeft = parseFloat(computed.borderLeftWidth);
let borderWidth = borderLeft + parseFloat(computed.borderRightWidth);
let borderTop = parseFloat(computed.borderTopWidth);
let borderHeight = borderTop + parseFloat(computed.borderBottomWidth);
let paddingLeft = parseFloat(computed.paddingLeft);
let paddingWidth = paddingLeft + parseFloat(computed.paddingRight)
let paddingTop = parseFloat(computed.paddingTop);
let paddingHeight = paddingTop + parseFloat(computed.paddingBottom);
// get the current bounding rect, including the border-box
let rect = element.getBoundingClientRect();
// we need to get the current scale since the computed values don't know about it...
let scale = 1 / (element.offsetHeight / rect.height);
// the real displayed height and width without border nor padding
let height = rect.height - ((borderHeight + paddingHeight) * scale);
let width = rect.width - ((borderWidth + paddingWidth) * scale);
// create our rectangle
marker = document.createElement('div');
marker.classList.add('marker');
marker.style.height = height + 'px';
marker.style.width = width + 'px';
// we need to scale border + padding again
marker.style.top = (rect.top + (borderTop + paddingTop) * scale) + 'px';
marker.style.left = (rect.left + (borderLeft + paddingLeft) * scale) + 'px';
document.body.append(marker);
}
#element {
width: 250px;
border: 0.5em solid green;
padding: 0.5em;
margin-top: 12px;
}
#element.scaled {
transform: scale(2);
transform-origin: top left;
}
#element.boxSized {
box-sizing: border-box;
}
.marker {
position: fixed;
width: 3px;
background: rgba(0, 0, 0, .3)
}
<label>scale
<input id="scale" type="checkbox">
</label>
<label>box-sizing
<input id="boxSizing" type="checkbox">
</label>
<div id="element">
Hello
<br> world
</div>
when you set box-sizing as border-box:
The width and height properties include the content, the padding and border, but not the margin
So when you use getComputedStyle to get a element's height, of course it includes the height of padding and border.
you can have a look at box-sizing property and css box model

Dynamically Centering Filtered Node within SVG Element in D3

I am working to add filter functionality to my d3 graph. When the user searches for a specific node based on label or id, I want to re-render the graph and show the entire graph again but I want the filtered node to sit in the center of the svg element.
here is what I have the helped it to be centered:
// I get the width and height of the SVG element:
var svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10);
var svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10);
// I get the center of the svg:
var centerX = svgWidth / 2;
var centerY = svgHeight / 2;
_.forEach(nodes, function(e) {
// get the full node (with x and y coordinates) based on the id
var nodeObject = g.node(nodeId);
// I look for matches between the nodeId or label and search word
if (searchInput) {
if (nodeObject.id === parseInt(searchInput, 10) || nodeObject.label.toUpperCase().indexOf(searchInput.toUpperCase()) > -1) {
searchedNodes.push(nodeObject);
console.log(searchedNodes);
}
}
}
// after looping through all the nodes rendered
if (searchedNodes.length > 0) {
//var width = searchedNodes[0].elem.getBBox().width;
//var height = searchedNodes[0].elem.getBBox().height;
ctrl.selectedNode = searchedNodes[0];
var offsetX = centerX - searchedNodes[0].x;
var offsetY = centerY - searchedNodes[0].y;
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")");
// this line here is incorrect syntax and breaks the build, essentially stopping the script from running
// the graph renders correctly when this line is here
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")").scale(2).event;
}
This is what the graph looks like with the line above that breaks the script included.
When I removed that line, it doesn't center, almost looking like over-renders the graph. Obviously I will need to remove the line of code above that is incorrect but does anybody no why the graph doesn't render correctly in this case?:
// get the user input and re-render the graph
elem.find(".search").bind("keyup", function (e:any) {
var searchInput;
if (e["keyCode"] === 13) {
searchedNodes = [];
searchInput = scope["searchInput"];
currentFilteredNode = null;
enterKeyPressed = true;
renderGraph(searchInput);
}
if (e["keyCode"] === 8) {
searchedNodes = [];
searchInput = scope["searchInput"];
currentFilteredNode = null;
renderGraph(searchInput);
}
});
// if there is searchInput and at least one matching node sort the nodes
// by id and then select and center the first matching one
if (searchInput && searchedNodes.length > 0) {
searchedNodes.sort(function (node1:any, node2:any) {
return node1.id - node2.id;
});
// make sure the noResultsMessage does not get shown on the screen if there are matching results
scope.$apply(function() {
scope["noResultsMessage"] = false;
});
ctrl.selectedNode = searchedNodes[0];
offsetX = centerX - searchedNodes[0].x;
offsetY = centerY - searchedNodes[0].y;
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")");
}
// the only other zoom and this runs just on page load
zoom = d3.behavior.zoom();
zoom.on("zoom", function() {
svgGroup.attr("transform", "translate(" + (<any>d3.event).translate + ")" + "scale(" + (<any>d3.event).scale + ")");
// this scales the graph - it runs on page load and whenever the user enters a search input, which re-renders the whole graph
var scaleGraph = function(useAnimation:any) {
var graphWidth = g.graph().width + 4;
var graphHeight = g.graph().height + 4;
var width = parseInt(svg.style("width").replace(/px/, ""), 10);
var height = parseInt(svg.style("height").replace(/px/, ""), 10);
var zoomScale = originalZoomScale;
// Zoom and scale to fit
if (ctrl.autoResizeGraph === "disabled") {
zoomScale = 1;
} else {
// always scale to canvas if set to fill or if auto (when larger than canvas)
if (ctrl.autoResizeGraph === "fill" || (graphWidth > width || graphHeight > height)) {
zoomScale = Math.min(width / graphWidth, height / graphHeight);
}
}
var translate;
if (direction.toUpperCase() === "TB") {
// Center horizontal + align top (offset 1px)
translate = [(width / 2) - ((graphWidth * zoomScale) / 2) + 2, 1];
} else if (direction.toUpperCase() === "BT") {
// Center horizontal + align top (offset 1px)
translate = [(width / 2) - ((graphWidth * zoomScale) / 4) + 2, 1];
} else if (direction.toUpperCase() === "LR") {
// Center vertical (offset 1px)
translate = [1, (height / 2) - ((graphHeight * zoomScale) / 2)];
} else if (direction.toUpperCase() === "RL") {
// Center vertical (offset 1px)
translate = [1, (height / 2) - ((graphHeight * zoomScale) / 4)];
} else {
// Center horizontal and vertical
translate = [(width / 2) - ((graphWidth * zoomScale) / 2), (height / 2) - ((graphHeight * zoomScale) / 2)];
}
zoom.center([width / 2, height / 2]);
zoom.size([width, height]);
zoom.translate(translate);
zoom.scale(zoomScale);
// If rendering the first time, then don't use animation
zoom.event(useAnimation ? svg.transition().duration(500) : svg);
};
CODE FOR FILTERING THE NODES:
// move to the left of the searchedNodes array when the left arrow is clicked
scope["filterNodesLeft"] = function () {
filterNodesIndex--;
if (filterNodesIndex < 0) {
filterNodesIndex = searchedNodes.length - 1;
}
currentFilteredNode = searchedNodes[filterNodesIndex];
runScaleGraph = true;
number = 1;
renderGraph();
};
// move to the right of the searchNodes array when the right arrow is clicked
scope["filterNodesRight"] = function () {
filterNodesIndex++;
if (filterNodesIndex > searchedNodes.length - 1) {
filterNodesIndex = 0;
}
currentFilteredNode = searchedNodes[filterNodesIndex];
runScaleGraph = true;
number = 1;
renderGraph();
};
// get the current filteredNode in the searchNodes array and center it
// when the graph is re-rendered
if (currentFilteredNode) {
ctrl.selectedNode = currentFilteredNode;
offsetX = centerX - currentFilteredNode.x;
offsetY = centerY - currentFilteredNode.y;
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")");
runScaleGraph = false;
}
You will want to find the x and y coordinates of your target node, and set the transform attribute of your group with class 'output' accordingly. You will also need to know the width and height of 'output' in order to position it such that your target node is in the center.
//when diagram is initially displayed
var output = d3.select('.output');
var bbox = output.getBBox();
var centerX = bbox.width * .5;
var centerY = bbox.height * .5;
//in your block where you find a node matches the filter
if (node.label.toUpperCase().indexOf(searchString.toUpperCase()) > -1) {
var offsetX = centerX - node.x;
var offsetY = centerY - node.y;
output.attr('transform', 'translate(' + offsetX + ',' + offsetY + ')');
}
Depending on the node's registration point, you may also need to take in to account the node's width and height to make sure we are directly centered on the node. For example, if the registration point is the top left of the node, you would want to add half the nodes width and half the nodes height to the offset.
-- Edit --
In the following line:
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")");
by including "scale(" + 3 + ")" so you are scaling your entire graph - you are not 'zooming in' on the place you have centered, rather the content itself is bigger and so offsetX and offsetY are not the correct cordinates to center on.
The reason things look better when you add that other line, is that you are removing the scale.
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")");
So, we are back to the default scale, immediately prior to your error being thrown.
If you want to scale, you'll need to multiply offsetX and offsetY by whatever you want to scale by.
If you do not want to scale, just remove
"scale(" + 3 + ")"
Here's how I solved it:
// zoom in on the searched or filtered node
function zoomOnNode (node:any) {
// get the width and height of the svg
var svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10);
var svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10);
// loop through all the rendered nodes (these nodes have x and y coordinates)
for (var i = 0; i < renderedNodes.length; i++) {
// if the first matching node passed into the function
// and the renderedNode's id match get the
// x and y coordinates from that rendered node and use it to calculate the svg transition
if (node.id === renderedNodes[i].id) {
var translate = [svgWidth / 2 - renderedNodes[i].x, svgHeight / 2 - renderedNodes[i].y];
var scale = 1;
svg.transition().duration(750).call(zoom.translate(translate).scale(scale).event);
}
}
}
// listen for the enter key press, get all matching nodes and pass in the first matching node in the array to the zoomOnNode function
elem.find(".search").bind("keyup", function (e:any) {
var searchInput;
if (e["keyCode"] === 13) {
searchedNodes = [];
searchInput = scope["searchInput"];
enterKeyPressed = true;
if (searchInput) {
// recursively get all matching nodes based on search input
getMatchingNodes(ctrl.nodes, searchInput);
scope.$apply(function() {
// show the toggle icons if searchedNodes.length is greater then 1
scope["matchingNodes"] = searchedNodes.length;
scope["noResultsMessage"] = false;
if (searchedNodes.length > 0) {
var firstNode = searchedNodes[0];
ctrl.selectedNode = firstNode;
zoomOnNode(firstNode);
} else if (searchedNodes.length === 0) {
ctrl.selectedNode = null;
// add the noResultsMessage to the screen
scope["noResultsMessage"] = true;
}
});
}
}
}

How to position a set in Raphael SVG?

I am trying to position a set of elements including text, path and circle in raphael. Is there a way to position the entire set in the svg? The SVG itself covers the entire height and width of the window and the set needs to be positioned in the center of the SVG.
The code is as follows:
var r = Raphael("holder", windowWidth, windowHeight);
var set = r.set(
(r.text(100,250,"ab").attr({...})),
(r.text(295,250,"c").attr({..})),
(r.path("M400,217L400,317").attr({...})),
(r.circle(190, 290, 13).attr({...})));
for (var i=0; i<set.length; i++) {
set[i].node.setAttribute("class", "set");
}
I've added the class in case the set could be manipulated using css, but I havent found any method to do so.
Any ideas on how to solve this problem?
You can do a transform on a set, like http://jsbin.com/EzUFiD/1/edit?html,js,output (fiddle isn't working for me)
var svgWidth = 600;
var svgHeight = 600;
var r = Raphael("holder", svgWidth, svgHeight);
var set = r.set(
(r.text(50,50,"ab")),
(r.text(50,60,"c")),
(r.path("M100,17L40,31")),
(r.circle(80, 80, 13))
);
var bbox = set.getBBox(); //find surrounding rect of the set
var posx = svgWidth / 2 - bbox.width / 2 - bbox.x; // find new pos depending on where it is initially
var posy = svgHeight / 2 - bbox.height / 2 - bbox.y;
set.transform('t' + posx + ',' + posy);

Cropping an image with a preview using jcrop

I'm using jcrop and trying to make a "live" preview of the cropped area on an image.
The movement of the selected area works perfectly if the "Crop Selection" area is the same height and width as the destination preview div.
Check out the issue here: http://jsfiddle.net/fbaAW/
function showCoords(c)
{
var $this = this.ui.holder;
var original = $this.prev();
var preview = original.parent().find(".image");
var oH = original.height();
var oW = original.width();
var pH = preview.height();
var pW = preview.width();
var sH = c.h;
var sW = c.w;
var differenceH = pH - sH;
var differenceW = pW - sW;
//preview.css('width', c.w);
//preview.css('height', c.h);
//preview.css("background-size", Math.round(oW + differenceW) + "px" + " " + Math.round(oH + differenceH) + "px");
preview.css("background-position", Math.round(c.x) * -1 + "px" + " " + Math.round(c.y) * -1 + "px");
}
As you can see, I've commented out a few of my tests and attempts at getting this code to work properly but I just can't wrap my head around the relationship between the position and the size background properties in order to get this effect to work correctly.
Calculate the horizontal and vertical ratios between the selection size and the preview area size:
var rW = pW / c.w;
var rH = pH / c.h;
Then apply them to the background-size and background-position:
preview.css("background-size", (oW*rW) + "px" + " " + (oH*rH) + "px");
preview.css("background-position", rW * Math.round(c.x) * -1 + "px" + " " + rH * Math.round(c.y) * -1 + "px");
http://jsfiddle.net/fbaAW/1/
So, if the preview size is, say, 3 times the size of your jCrop selection area, it means you have scale the original image by 3, and compensate for the scaling when defining the background position.

Pinch to zoom with CSS3

I'm trying to implement pinch-to-zoom gestures exactly as in Google Maps. I watched a talk by Stephen Woods - "Creating Responsive HTML5 Touch Interfaces” - about the issue and used the technique mentioned. The idea is to set the transform origin of the target element at (0, 0) and scale at the point of the transform. Then translate the image to keep it centered at the point of transform.
In my test code scaling works fine. The image zooms in and out fine between subsequent translations. The problem is I am not calculating the translation values properly. I am using jQuery and Hammer.js for touch events. How can I adjust my calculation in the transform callback so that the image stays centered at the point of transform?
The CoffeeScript (#test-resize is a div with a background image)
image = $('#test-resize')
hammer = image.hammer ->
prevent_default: true
scale_treshold: 0
width = image.width()
height = image.height()
toX = 0
toY = 0
translateX = 0
translateY = 0
prevScale = 1
scale = 1
hammer.bind 'transformstart', (event) ->
toX = (event.touches[0].x + event.touches[0].x) / 2
toY = (event.touches[1].y + event.touches[1].y) / 2
hammer.bind 'transform', (event) ->
scale = prevScale * event.scale
shiftX = toX * ((image.width() * scale) - width) / (image.width() * scale)
shiftY = toY * ((image.height() * scale) - height) / (image.height() * scale)
width = image.width() * scale
height = image.height() * scale
translateX -= shiftX
translateY -= shiftY
css = 'translateX(' + #translateX + 'px) translateY(' + #translateY + 'px) scale(' + scale + ')'
image.css '-webkit-transform', css
image.css '-webkit-transform-origin', '0 0'
hammer.bind 'transformend', () ->
prevScale = scale
I managed to get it working.
jsFiddle demo
In the jsFiddle demo, clicking on the image represents a pinch gesture centred at the click point. Subsequent clicks increase the scale factor by a constant amount. To make this useful, you would want to make the scale and translate computations much more often on a transform event (hammer.js provides one).
The key to getting it to work was to correctly compute the point of scale coordinates relative to the image. I used event.clientX/Y to get the screen coordinates. The following lines convert from screen to image coordinates:
x -= offset.left + newX
y -= offset.top + newY
Then we compute a new size for the image and find the distances to translate by. The translation equation is taken from Stephen Woods' talk.
newWidth = image.width() * scale
newHeight = image.height() * scale
newX += -x * (newWidth - image.width) / newWidth
newY += -y * (newHeight - image.height) / newHeight
Finally, we scale and translate
image.css '-webkit-transform', "scale3d(#{scale}, #{scale}, 1)"
wrap.css '-webkit-transform', "translate3d(#{newX}px, #{newY}px, 0)"
We do all our translations on a wrapper element to ensure that the translate-origin stays at the top left of our image.
I successfully used that snippet to resize images on phonegap, using hammer and jquery.
If it interests someone, i translated this to JS.
function attachPinch(wrapperID,imgID)
{
var image = $(imgID);
var wrap = $(wrapperID);
var width = image.width();
var height = image.height();
var newX = 0;
var newY = 0;
var offset = wrap.offset();
$(imgID).hammer().on("pinch", function(event) {
var photo = $(this);
newWidth = photo.width() * event.gesture.scale;
newHeight = photo.height() * event.gesture.scale;
// Convert from screen to image coordinates
var x;
var y;
x -= offset.left + newX;
y -= offset.top + newY;
newX += -x * (newWidth - width) / newWidth;
newY += -y * (newHeight - height) / newHeight;
photo.css('-webkit-transform', "scale3d("+event.gesture.scale+", "+event.gesture.scale+", 1)");
wrap.css('-webkit-transform', "translate3d("+newX+"px, "+newY+"px, 0)");
width = newWidth;
height = newHeight;
});
}
I looked all over the internet, and outernet whatever, until I came across the only working plugin/library - http://cubiq.org/iscroll-4
var myScroll;
myScroll = new iScroll('wrapper');
where wrapper is your id as in id="wrapper"
<div id="wrapper">
<img src="smth.jpg" />
</div>
Not a real answer, but a link to a plug=in that does it all for you. Great work!
https://github.com/timmywil/jquery.panzoom
(Thanks 'Timmywil', who-ever you are)
This is something I wrote a few years back in Java and recently converted to JavaScript
function View()
{
this.pos = {x:0,y:0};
this.Z = 0;
this.zoom = 1;
this.scale = 1.1;
this.Zoom = function(delta,x,y)
{
var X = x-this.pos.x;
var Y = y-this.pos.y;
var scale = this.scale;
if(delta>0) this.Z++;
else
{
this.Z--;
scale = 1/scale;
}
this.zoom = Math.pow(this.scale, this.Z);
this.pos.x+=X-scale*X;
this.pos.y+=Y-scale*Y;
}
}
The this.Zoom = function(delta,x,y) takes:
delta = zoom in or out
x = x position of the zoom origin
y = y position of the zoom origin
A small example:
<script>
var view = new View();
var DivStyle = {x:-123,y:-423,w:300,h:200};
function OnMouseWheel(event)
{
event.preventDefault();
view.Zoom(event.wheelDelta,event.clientX,event.clientY);
div.style.left = (DivStyle.x*view.zoom+view.pos.x)+"px";
div.style.top = (DivStyle.y*view.zoom+view.pos.y)+"px";
div.style.width = (DivStyle.w*view.zoom)+"px";
div.style.height = (DivStyle.h*view.zoom)+"px";
}
function OnMouseMove(event)
{
view.pos = {x:event.clientX,y:event.clientY};
div.style.left = (DivStyle.x*view.zoom+view.pos.x)+"px";
div.style.top = (DivStyle.y*view.zoom+view.pos.y)+"px";
div.style.width = (DivStyle.w*view.zoom)+"px";
div.style.height = (DivStyle.h*view.zoom)+"px";
}
</script>
<body onmousewheel="OnMouseWheel(event)" onmousemove="OnMouseMove(event)">
<div id="div" style="position:absolute;left:-123px;top:-423px;width:300px;height:200px;border:1px solid;"></div>
</body>
This was made with the intention of being used with a canvas and graphics, but it should work perfectly for normal HTML layout

Categories

Resources