jQuery Panzoom plugin positioning image on right with contain:'invert' - javascript

I'm trying to use the jQuery Panzoom plugin, which is mostly fine, except...
I want the large image to initially be scaled to fit in the container, and have contain: 'invert' enabled. I've adapted this example to scale and position the image when it first loads.
This works fine, with the image positioned in the center of the (window-sized) container. But if I use contain: 'invert' as a Panzoom option, the image is positioned on the right. I can't work out why.
You can see it in action here: https://jsfiddle.net/7ugm9z51/2/
Here's the HTML/CSS:
.zoom-container {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
}
<div class="zoom-container">
<img class="zoom-img" src="https://farm8.staticflickr.com/7314/15854478283_d2667a7264_o.jpg">
</div>
And the JavaScript:
$('.zoom-img').panzoom({
// Causes image to appear aligned right:
contain: 'invert',
});
// Get container dimensions
var container_height = $('.zoom-container').height();
var container_width = $('.zoom-container').width();
// Get image dimensions
var image_height = $('.zoom-img').height();
var image_width = $('.zoom-img').width();
// Calculate the center of image since origin is at x:50% y:50%
var image_center_left = image_width / 2.0;
var image_center_top = image_height / 2.0;
// Calculate scaling factor
var zoom_factor;
// Check to determine whether to stretch along width or height
if(image_height > image_width) {
zoom_factor = container_height / image_height;
} else {
zoom_factor = container_width / image_width;
};
// Zoom by zoom_factor
$('.zoom-img').panzoom('option', 'minScale', zoom_factor);
$('.zoom-img').panzoom('zoom', zoom_factor, {animate: false});
// Calculate new image dimensions after zoom
image_width = image_width * zoom_factor;
image_height = image_height * zoom_factor;
// Calculate offset of the image after zoom
var image_offset_left = image_center_left - (image_width / 2.0);
var image_offset_top = image_center_top - (image_height / 2.0);
// Calculate desired offset for image
var new_offset_left = (container_width - image_width) / 2.0;
var new_offset_top = (container_height - image_height) / 2.0;
// Pan to set desired offset for image
var pan_left = new_offset_left - image_offset_left;
var pan_top = new_offset_top - image_offset_top;
$('.zoom-img').panzoom('pan', pan_left, pan_top);

Related

How to align dom-to-image capture of Leaflet map to map's bounds?

I'm working on a project in which i'm capturing map screenshot (using dom-to-image library) and sending it to an external API which is returning back some coordinates(x,y,w,h) after processing the sent image. These co-ordinates(rectangles) i'm trying to draw on leaflet.
As the captured image size is bigger than width and height of captured area (don't know why), I need to do scaling the co-ordinates.
Now the problem is Leaflet rectangles are not drawing on accurate position which external API is returning.
However, I'm sure that the external API is returning correct coordinates(x,y,w,h) with respect to sent image's width & height.
Something wrong i'm doing on scaling coordinates.
Below is the code snippet i'm trying:
domtoimage.toPng(document.querySelector("#map"))
.then(function (dataUrl) {
var boundsOnly = map.getBounds();
let topLeft = boundsOnly.getNorthWest();
let topRight = boundsOnly.getNorthEast();
let bottomLeft = boundsOnly.getSouthWest();
let bottomRight = boundsOnly.getSouthEast();
var currBBOXpoints = { x1y1: map.latLngToLayerPoint(topLeft),
x2y2: map.latLngToLayerPoint(topRight),
x3y3: map.latLngToLayerPoint(bottomRight),
x4y4: map.latLngToLayerPoint(bottomLeft) };
var pW = currBBOXpoints.x2y2.x - currBBOXpoints.x1y1.x;
var pH = currBBOXpoints.x3y3.y - currBBOXpoints.x1y1.y;
currBBOXpoints.pW = pW; //calculated the width of captured area
currBBOXpoints.pH = pH; //calculated the height of captured area
var i = new Image();
i.onload = function () { //calculating captured image's actual width, height
$.ajax({
type: 'post',
url: '/externalapi',
data: JSON.stringify(dataUrl),
contentType: "application/json",
dataType: "json",
success: function (resultData) {
resultData["iW"] = i.width; //captured image width
resultData["iH"] = i.height; //captured image height
resultData["currBBOXpoints"] = currBBOXpoints; //captured area bounds
drawRects(resultData);
//NOTE: Captured image's width and height are bigger than width and height of captured area (don't know why)
}
});
};
i.src = dataUrl;
});
function drawRects(rectData) {
var scale = Math.max(rectData.currBBOXpoints.pW / rectData['iW'], rectData.currBBOXpoints.pH / rectData['iH']);
var shifted_x = rectData.currBBOXpoints.pW / 2 - rectData['iW'] / 2 * scale;
var shifted_y = rectData.currBBOXpoints.pH / 2 - rectData['iH'] / 2 * scale;
rectData.od.forEach(rc => {
var modifiedX = Number(rc['x']) * scale + shifted_x;
var modifiedY = Number(rc['y']) * scale + shifted_y;
var modifiedW = (modifiedX + rc['w'])
var modifiedH = (modifiedY + rc['h'])
let point3 = map.layerPointToLatLng(L.point(modifiedX, modifiedY));
let point4 = map.layerPointToLatLng(L.point(modifiedW, modifiedH));
var rectBounds = [[point3.lat, point3.lng], [point4.lat, point4.lng]];
var boundingBox = L.rectangle(rectBounds, { color: "yellow", weight: 1, name: "rect", fillOpacity: 0.10 });
map.addLayer(boundingBox);
});
}
I can't say why but when you set the scale to 0.9 after defining x and y it matches perfect.
var scale = Math.max(currBBOXpoints.pW / imagesize.width, currBBOXpoints.pH / imagesize.height);
var x = currBBOXpoints.pW / 2 - imagesize.width / 2 * scale;
var y = currBBOXpoints.pH / 2 - imagesize.height / 2 * scale;
scale = 0.9
Also you have to set in the fiddle the css width and height:
#mapWrapper{
padding: 10px;
margin: 10px;
width: 1744px;
height: 854px;
}
This is an XY problem (which you successfully identified: the size of the capture is not the size of the map container), so let's fix the root problem instead.
It looks like dom-to-image aligns the top-left corner of the capture, but also takes into account the size of any overflowing elements (such as partially-visible map tiles on the southeast corner) when calculating the size of the capture:
(This can be proven by playing a bit with the browser's box model inspector, comparing the position & size of the southeasternmost tile with the size of the capture)
Since the capture is aligned top-left, you can just clip the capture to the size of the map canvas.
You can use the width and height options of dom-to-image together with the getSize method of L.Map to force clipping the captured image to the dimensions of the map canvas, e.g.:
domtoimage.toPng(document.querySelector("#leaflet"), {
width: map.getSize().x,
height: map.getSize().y
})
See a working demo.

Why is my jQuery resize firing inconsistently?

I'm wrapping up a site that involves a few elements (image / text / diagonal line) that have to scale proportionately on different screens.
Because there's text that has to be resized, I'm using jQuery to calculate the measurements for all of the elements based on a ratio. This was the best solution I could think of at the time, and with a deadline approaching, I think I'm stuck with it. It's a single-page site that scrolls by the page (e.g., full pages in the viewport).
Here's a link to the demo site
The idea behind the code:
We check the height of the viewport to set the container size
Set the wrapper element height, based on the container size and necessary
margins
Set the width based on a ratio
Use these values to calculate font size, image size, and offsets
As the screen is re-sized, the element shrinks proportionately to fill the available space.
It looks kind of like this:
There are two panels like this. I re-use the same code (with different variable names, and a few sizing differences) for the second panel.
Here's my Javascript/jQuery for the first:
// Set panel height on page load & resize
$(window).on("resize", function () {
var $panelHeight = $(window).height();
var $headerHeight = $('.banner').height();
// General height for panels
$('.bg-panel').css('height', $panelHeight );
$('.bg-panel').css('padding-top', $headerHeight);
}).resize();
// We want to scale content proportionately
// First let's get some breakpoints
var $breakPoint = 768;
var $breakPointSM = 480;
// Panel 1
$(window).on("resize", function () {
// Check height of current panel
// If on single-column view, we want to measure the space between the text column and bottom of screen
// Otherwise, height of entire panel
var $windowHeight = $('.panel-test').height();
// But we need to subtract the header height, so our math is correct
var $headerHeight = $('.banner').height();
var $windowHeight = $windowHeight - $headerHeight;
// Now we have the correct height to work with
// We're at 768px or below, subtract the text element from the overall height
if ( $(document).width() <= $breakPoint) {
var $heightofDiv = $('.panel-1-text').height();
var $mobileHeight = $windowHeight - $heightofDiv;
var $windowHeight = $mobileHeight;
}
// Save the window height for calculating our margins!
var $windowHeightforMargins = $windowHeight;
// Top and bottom margins
var $marginTop = $windowHeight * (102/792); // ratio from PSD
var $marginBottom = $windowHeight * (84/792); // ratio from PSD
var $marginTotal = $marginTop + $marginBottom;
// Responsive solution
// As browser shrinks, reduce the height of panel so it produces a smaller container
if ( $(document).width() > 1200 && $(document).width() <= 1440) {
var $windowHeight = $windowHeight * 0.9;
var $marginTop = $marginTop * 2;
}
else if ( $(document).width() > 990 && $(document).width() <= 1200) {
var $windowHeight = $windowHeight * 0.8;
var $marginTop = $marginTop * 3;
}
else if ( $(document).width() > $breakPoint && $(document).width() <= 990) {
var $windowHeight = $windowHeight * 0.7;
var $marginTop = $marginTop * 3.5;
}
else if ( $(document).width() < $breakPoint) { // Ratio here goes up again because we're accounting for new height with $mobileHeight
var $windowHeight = $windowHeight * 0.8;
}
// This ratio determines the width of the container
var $ratio = 697 / 607; // from PSD
// Set container height, depending on height of panel
if ( $(document).width() <= $breakPointSM) {
var $taglinesHeight = ($windowHeight * 1.5); // Scale up for phones
}
else if ( $(document).width() > $breakPointSM && $(document).width() <= $breakPoint ){
var $taglinesHeight = ($windowHeight * 1); // Scale down for tablet
}
else {
var $taglinesHeight = $windowHeight - $marginTotal;
}
// Set container width as ratio of height
if ( $(document).width() <= $breakPoint) {
var $taglinesWidth = $taglinesHeight * $ratio
} else {
var $taglinesWidth = $taglinesHeight * $ratio
}
$('.panel-test .bg-taglines').css("width", $taglinesWidth);
$('.panel-test .bg-taglines').css("height", $taglinesHeight);
// Add top margin if above breakpoint
if ( $(document).width() > $breakPoint) { // No margin unless above 768px
$('.panel-test .bg-taglines').css("margin-top", $marginTop);
}
else {
$('.panel-test .panel-1-tagline').css("bottom", $marginTop);
}
// Set font size
var $fontSize = $taglinesWidth * 0.12;
$('.bg-panel h4').css("font-size", $fontSize);
// Set pink line origin (relative to bottom-left of frame)
var $pinkX = $taglinesWidth * (286 / 705);
var $pinkY = $taglinesHeight * (192 / 607);
$('.panel-test .animation-wrapper').css("left", $pinkX);
$('.panel-test .animation-wrapper').css("bottom", $pinkY);
// Set image size
var $imageWidth = $taglinesWidth * 0.556;
$('.panel-test .scaleable-image').css("width", $imageWidth);
// Set h3 margin from top
if ( $(document).width() >= $breakPoint) {
var $marginH3 = $windowHeight * (217/792); // ratio from PSD
$('.panel-test h3').css("margin-top", $marginH3);
} else {
// CSS
}
// Set line offset from top
var $lineOffset = $taglinesHeight * 0.7;
$('.panel-test .line-wrapper').css("top", $lineOffset);
// Set line length
var $lineLong = $taglinesWidth * 1;
$('.panel-test .pink-line').css("width", $lineLong);
}).resize();
It works: MOST of the time.
If I drag my window to resize, some of the elements get resized. Others don't.
A page refresh generally solves it, but right now, elements (mostly the images!) just aren't scaling properly and in sync with other elements.
I'm very new to jQuery and this is my first big undertaking. New to using resize as well. Hoping I just made a goof that's easy to fix.
Thanks!
LIVE SITE LINK
Other plugins in use: jQuery Scrollify (for full page scrolling) and ScrollReveal.
Guess I can answer my own question.
The issue seemed to be that the values were getting mixed up when scrolling from one full-screen panel to another.
Changing this:
$(window).on("resize", function () {
To this:
$(window).on("resize load scroll", function (e) {
... solved the issue. I'm not sure if it's the right way to do it, but the resizes are all working fine now.

How to shrink an image width based on scroll position

I'm looking to shrink a logo based on scroll
So far, I have something like this
logoSize = function(){
var headerOffset = $(window).height() - 650;
var maxScrollDistance = 1300;
$(window).scroll(function() {
var percentage = maxScrollDistance / $(document).scrollTop();
if (percentage <= headerOffset) {
$('.logo').css('width', percentage * 64);
}
console.log(percentage);
});
}
logoSize();
I'm close, but the image either starts too wide or it shrinks too quickly, I need it to happen for the first 650px of scroll as you can see - Any ideas? Perhaps a percentage width would be better?
I've re-written your code based on the assumption that you have a target size in mind , e.g. after scrolling 650px you want your image to be 250px wide.
It scrolls smoothly between the native size and the target size, and takes into account the fact that the window height could be less than your maximum scrolling distance:
logoSize = function () {
// Get the real width of the logo image
var theLogo = $("#thelogo");
var newImage = new Image();
newImage.src = theLogo.attr("src");
var imgWidth = newImage.width;
// distance over which zoom effect takes place
var maxScrollDistance = 650;
// set to window height if that is smaller
maxScrollDistance = Math.min(maxScrollDistance, $(window).height());
// width at maximum zoom out (i.e. when window has scrolled maxScrollDistance)
var widthAtMax = 500;
// calculate diff and how many pixels to zoom per pixel scrolled
var widthDiff = imgWidth - widthAtMax;
var pixelsPerScroll =(widthDiff / maxScrollDistance);
$(window).scroll(function () {
// the currently scrolled-to position - max-out at maxScrollDistance
var scrollTopPos = Math.min($(document).scrollTop(), maxScrollDistance);
// how many pixels to adjust by
var scrollChangePx = Math.floor(scrollTopPos * pixelsPerScroll);
// calculate the new width
var zoomedWidth = imgWidth - scrollChangePx;
// set the width
$('.logo').css('width', zoomedWidth);
});
}
logoSize();
See http://jsfiddle.net/raad/woun56vk/ for a working example.

jQuery Plug-in to handle mousemove/drag with easing on a hidden div (kind of "inner zoom"

Actually I'm looking for a jQuery plug-in that can handle this:
there is a container with overflow hidden
inside of this is another one, which is way larger
when i move over the div, the part I'm seeing depends on my current position
when I'm in the left top corner I see the top left corner of the inner container
when I'm in the middle i see the middle of the container …
I wrote a little JavaScript that does that:
this.zoom.mousemove( function(event) {
var parentOffset = $(this).parent().offset();
var relativeX = event.pageX - parentOffset.left;
var relativeY = event.pageY - parentOffset.top;
var differenceX = that.zoom.width() - that.pageWidth;
var differenceY = that.zoom.height() - that.pageHeight;
var percentX = relativeX / that.pageWidth;
var percentY = relativeY / that.pageHeight;
if (1 < percentX) {
percentX = 1;
}
if (1 < percentY) {
percentY = 1;
}
var left = percentX * differenceX;
var top = percentY * differenceY;
that.zoom.css('left', -left).css('top', -top);
});
But this isn't very smooth and kinda jumpy, when you enter from another point of the container. So, before reinventing the wheel: Is there one plug in, that does exactly that and has iOS support (drag instead of mouse move)? All zoom plug ins (like Cloud Zoom) are over the top for this purpose and most have no support for dragging on iOS.
And if there's not something like this. How can I make this smoother and cooler. Any approach would be helpful. :)
Many thanks.
So, here is my solution - which works pretty well and is easy to achieve. There could be done some improvement, but to get the idea i'll leave it that way. :)
there is a demo on jsfiddle:
http://jsfiddle.net/insertusernamehere/78TJc/
CSS
<style>
div.zoom_wrapper {
position: relative;
overflow: hidden;
width: 500px;
height: 500px;
border: 1px solid #ccc;
cursor: crosshair;
}
div.zoom_wrapper > * {
position: absolute;
}
</style>
HTML
<div class="zoom_wrapper">
<img id="zoom" src="http://lorempixel.com/output/people-q-c-1020-797-9.jpg" alt="">
</div>
JAVASCRPT
<script>
var zoom = null;
// this function will work even if the content has changed
function move() {
// get current position
var currentPosition = zoom.position();
var currentX = currentPosition.left;
var currentY = currentPosition.top;
// get container size
var tempWidth = zoom.parent().width();
var tempHeight = zoom.parent().height();
// get overflow
var differenceX = zoom.width() - tempWidth;
var differenceY = zoom.height() - tempHeight;
// get percentage multiplied by difference (in pixel) cut by percentage (here 1/4) that is used as "smoothness factor"
var tempX = zoom.data('x') / tempWidth * differenceX / 4;
var tempY = zoom.data('y') / tempHeight * differenceY / 4;
// get real top and left values to move to and the last factor slows it down and gives the smoothness (and it's corresponding with the calculation before)
var left = (tempX - currentX) / 1.25;
var top = (tempY - currentY) / 1.25;
// finally apply the new values
zoom.css('left', -left).css('top', -top);
}
$(document).ready(function () {
zoom = $('#zoom');
//handle mousemove to zoom layer - this also works if it is not located at the top left of the page
zoom.mousemove( function(event) {
var parentOffset = $(this).parent().offset();
zoom.data('x', event.pageX - parentOffset.left);
zoom.data('y', event.pageY - parentOffset.top);
});
// start the action only if user is over the container
zoom.hover(
function() {
zoom.data('running', setInterval( function() { move(); }, 30) );
},
function() {
clearInterval(zoom.data('running'));
}
);
});
</script>
Note:
This one has, of course, no support for touch devices. But for that I use (again)/I can recommend the good old iScroll … :)

Javascript (jQuery) Scale an Image to Its Container's Center Point

This seems like it should be quite simple, but for some reason I can't quite wrap my brain around it. I have an image inside a "viewport" div, of which the overflow property is set to hidden.
I've implemented a simple zooming and panning with jQuery UI, however I am having trouble getting the zoom to appear to originate from the center of the viewport. I did a little screencast from Photoshop the effect I'm trying to reproduce: http://dl.dropbox.com/u/107346/share/reference-point-zoom.mov
In PS you can adjust the scaling reference point an the object will scale from that point. Obviously this is not possible with HTML/CSS/JS, so I'm trying to find the appropriate left and top CSS values to mimic the effect.
Here is the code in question, with a few unnecessary bits removed:
html
<div id="viewport">
<img id="map" src="http://dl.dropbox.com/u/107346/share/fake-map.png" alt="" />
</div>
<div id="zoom-control"></div>
javascript
$('#zoom-control').slider({
min: 300,
max: 1020,
value: 300,
step: 24,
slide: function(event, ui) {
var old_width = $('#map').width();
var new_width = ui.value;
var width_change = new_width - old_width;
$('#map').css({
width: new_width,
// this is where I'm stuck...
// dividing by 2 makes the map zoom
// from the center, but if I've panned
// the map to a different location I'd
// like that reference point to change.
// So instead of zooming relative to
// the map image center point, it would
// appear to zoom relative to the center
// of the viewport.
left: "-=" + (width_change / 2),
top: "-=" + (width_change / 2)
});
}
});
Here is the project on JSFiddle: http://jsfiddle.net/christiannaths/W4seR/
Here's the working solution. I will explain the logic at the next edit.
Function Logic:
Summary: Remember the center position of the image, relatively.
The calculations for width and height are similar, I will only explain the height calculationThe detailled explanation is just an example of function logic. The real code, with different variable names can be found at the bottom of the answer.
Calculate the center (x,y) of the #map, relative to #viewport. This can be done by using the offset(), height() and width() methods.
// Absolute difference between the top border of #map and #viewport
var differenceY = viewport.offset().top - map.offset().top;
// We want to get the center position, so add it.
var centerPosition = differenceY + viewport.height() * 0.5;
// Don't forget about the border (3px per CSS)
centerPosition += 3;
// Calculate the relative center position of #map
var relativeCenterY = centerPosition / map.height();
// RESULT: A relative offset. When initialized, the center of #map is at
// the center of #viewport, so 50% (= 0.5)
// Same method for relativeCenterX
Calculate the new top and left offsets:
// Calculate the effect of zooming (example: zoom 1->2 = 2)
var relativeChange = new_width / old_width;
// Calculate the new height
var new_height = relativeChange * old_height;
// Calculate the `top` and `left` CSS properties.
// These must be negative if the upperleft corner is outside he viewport
// Add 50% of the #viewport's height to correctly position #map
// (otherwise, the center will be at the upperleft corner)
var newTopCss = -relativeCenterY * new_height + 0.5 * viewport.height();
Change the CSS property
map.css("top", newTopCss);
Demo: http://jsfiddle.net/W4seR/12/
var map = $('#map');
var viewport = $('#viewport');
// Cache the size of the viewport (300x300)
var viewport_size = {
x: viewport.width(),
y: viewport.height()
};
map.draggable();
$('#zoom-control').slider({
min: 300,
max: 1020,
value: 300,
step: 24,
create: function() {
map.css({
'width': 300,
'left': 0,
'top': 0
});
},
slide: function(event, ui) {
var old_width = map.width();
var old_height = map.height();
var viewport_offset = viewport.offset();
var offset = map.offset();
offset = {
top: viewport_offset.top - offset.top + .5*viewport_size.y +3,
left: viewport_offset.left - offset.left + .5*viewport_size.x +3
};
// Relative offsets, relative to the center!
offset.top = offset.top / old_height;
offset.left = offset.left / old_width;
var new_width = ui.value;
var relative = new_width / old_width;
var new_height = relative * old_height;
offset = {
top: -offset.top * new_height + .5*viewport_size.y,
left: -offset.left * new_width + .5*viewport_size.x
};
var css_properties = {
width: new_width,
left: offset.left,
top: offset.top
};
map.css(css_properties);
trace((map.position().left));
}
});
I have always relied on the kindness of strangers. Pertinent changes:
// Calculate the offset as a percentage, accounting for the height of the window
var x_offset = ((map.position().left-150))/(old_width/2);
var y_offset = ((map.position().top-150))/(old_width/2);
var css_properties = {
width: new_width,
// Set the offset based on the existing percentage rather than 1/2
// then readjust for the height of the window
left: (new_width * x_offset /2 ) + 150 + "px",
top: (new_width * y_offset /2 ) + 150 + "px"
};
Replace the hardcoded 150 with a variable set on viewport instantiation if necessary.
Here is a quick working version:
http://jsfiddle.net/flabbyrabbit/chLkZ/
Probably not the neatest solution but seems to work nicely, hope it helps.
Update: sorry this only works if zoom is 0 when the map is moved.

Categories

Resources