I have in interesting CSS/JavaScript problem. I am building a user-friendly web map that will include volunteered geographic information and needs to have the ability to print at multiple paper sizes, up to poster size. For the interface, I am working with Leaflet.js, Mapbox.js, and jQuery. The way I've approached printing is to set up a preview window that shows overlays only (no tileLayer) on a completely new L.Map with a white background, with the markers scaled proportionally to the paper size selected by the user. The idea is that the map will fill the page and the markers will always print at the same size (8 mm across for circle markers, 10 mm for icons). Here's a screenshot of the preview window in Firefox:
There is a fair bit of complex code. Suffice it to say that whenever the user changes the window size or paper orientation, the preview box and icons resize accordingly. Whenever the user changes the paper size, the icons resize but the preview box does not, so as to represent the correct size ratios. Here are the functions I'm using to do it:
function adjustPreviewBox(){
//set preview box dimensions based on print window size and paper orientation
if ($("#paperOrientation option[value=portrait]").prop("selected")){
var height = $("#printBox").height() - 61;
var width = height / Math.sqrt(2);
$("#printPreview").height(height);
$("#printPreview").width(width);
} else {
//first set by horizontal dimension
var width = $("#printBox").width() - 300;
var height = width / Math.sqrt(2);
//check for vertical overflow
if (height > $("#printBox").height() - 61){
height = $("#printBox").height() - 61;
width = height * Math.sqrt(2);
};
$("#printPreview").height(height);
$("#printPreview").width(width);
}
};
function adjustScale(){
//change symbol sizes and ratio scale according to paper size
var prevWidth = $("#printPreview").width();
var prevHeight = $("#printPreview").height();
var size = $("#paperSize select option:selected").val();
var series = size[0];
var pScale = Number(size[1]);
var longside, mmppPaper;
if (series == "A"){ //equations for long side lengths in mm, minus 10mm print margins
longside = Math.floor(1000/(Math.pow(2,(2*pScale-1)/4)) + 0.2) - 20;
} else if (series == "B"){
longside = Math.floor(1000/(Math.pow(2,(pScale-1)/2)) + 0.2) - 20;
};
//find the mm per pixel ratio
mmppPaper = prevWidth > prevHeight ? longside / prevWidth : longside / prevHeight;
var mapZoom = printPreviewMap.getZoom();
var scaleText = $("#printBox .leaflet-control-scale-line").html().split(" ");
var multiplier = scaleText[1] == "km" ? 1000000 : 1000;
var scalemm = Number(scaleText[0]) * multiplier;
var scalepx = Number($("#printBox .leaflet-control-scale-line").width());
var mmppMap = scalemm / scalepx;
var denominator = Math.round(mmppMap / mmppPaper);
$("#ratioScale span").text(denominator);
return [mmppMap, mmppPaper];
}
function resizeMarkers(markerType, init){
//scale preview marker size based on paper size and orientation
markerType == "circle" ? changeRadius(init) : changeIconSize(init);
};
function getRadius(){
//adjust ratio scale and return scale ratios
var scales = adjustScale();
var mmppPaper = scales[1];
return 4 / mmppPaper;
};
function changeRadius(init){
//each circle marker will print at 8 mm diameter regardless of map scale and page size
var radius = getRadius();
printPreviewMap.eachLayer(function(layer){
if (typeof layer._radius !== 'undefined'){
if (init == true){
layer.setStyle({
opacity: 1,
fillOpacity: 1
});
layer.unbindPopup();
};
layer.setRadius(radius);
}
});
};
function changeIconSize(init){
//each icon will print at 10 mm per side regardless of map scale and page size
var side = 2.5 * getRadius();
//need to change dimensions and offset
$("#printPreview .leaflet-marker-icon").css({
width: side + "px",
height: side + "px",
"margin-left": -(side / 2),
"margin-top": -(side / 2)
})
};
I have #media print CSS styles that seem to work well for printing the preview window:
#media print {
#page {
size: auto;
margin: 10mm;
}
#printBox, #printPreview {
position: absolute;
max-height: 100%;
bottom: 0;
left: 0;
top: 0;
right: 0;
}
#printPreview {
position: absolute !important;
width: 100% !important;
height: 100% !important;
border: none;
}
#scalegrip {
visibility: hidden;
}
#container {
visibility: hidden;
}
}
I've tested this by printing to a PDF using Adobe's driver. Here's the result:
It seems to work fine--except that the markers only fill the upper-left part of the page, whereas I would like them to expand outward to fill the entire page so that the final product is the same 'view' as the preview box. This is where I'm stumped and would welcome any advice or ideas from anyone who has tried something similar or knows their way around printing websites.
In a similar project, I had to force the map to refresh after any CSS size changes through the invalidateSize method. For example using jQuery to change map height and weight div:
$("map").css('width', '267mm');
$("map").css('height', '210mm');
map.invalidateSize();
According to leaflet help:
invalidateSize: Checks if the map container size changed and updates the map if so — call it after you've changed the map size dynamically, also animating pan by default.
Related
I'm calling an API that returns a URL to an image, the image could be any size and it's completely random.
I'm trying to resize images to fit within the page, ensuring the content is not pushed below the fold, or that the image doesn't hit the width of the page.
I've written some Javascript below, I've been testing it and am getting some strange results - the console logs are saying that the image is one size, but the element selector in Chrome's dev tools is usually saying something completely different. I'm sure I've made some basic mistake in my code, if you could take a look that would be great.
Javascript sets viewport height and width, checks if a photo src is available. Once the image has loaded, it checks if the natural dimensions are greater than that of the viewport, if so it attempts to resize - this is where the script is failing.
//check viewport
var viewportWidth = getWidth();
var viewportHeight = getHeight();
//get the media
if (data[2] == "photo") {
var tweetImage = document.getElementById("tweetImage");
//when it loads check the size against the browser size
tweetImage.onload = function () {
console.log('image height: ' + tweetImage.naturalHeight);
console.log('viewport height: ' + viewportHeight);
//does it matter if its landscape?
if (viewportWidth - tweetImage.naturalWidth < 1) {
tweetImage.width = Math.floor(tweetImage.naturalWidth - (viewportWidth - tweetImage.naturalWidth) * 1.2);
console.log('w');
} else if (Math.floor(viewportHeight - tweetImage.naturalHeight) < 1) {
console.log('h');
console.log(viewportHeight - tweetImage.naturalHeight);
console.log('changed result: ' + Math.floor(tweetImage.naturalHeight - (Math.abs(viewportHeight - tweetImage.naturalHeight))));
tweetImage.height = Math.floor(tweetImage.naturalHeight - (Math.abs(viewportHeight - tweetImage.naturalHeight)*1.2));
} else {
tweetImage.height = Math.floor(viewportHeight / 2);
}
tweetImage.align = "center";
tweetImage.paddingBottom = "10px";
};
//tweetImage.height = Math.floor(viewportHeight / 2);
tweetImage.src = data[3];
}
One option would be to use a CSS-based solution like viewport height units.
.example {
height: 50vh; // 50% of viewport height
}
See http://web-design-weekly.com/2014/11/18/viewport-units-vw-vh-vmin-vmax/
I want to overlay some text over a background image with background-size: cover.
Problem here is how do I keep the overlay div at the same position, relative to the background image, regardless of the window's size?
Here's a fiddle to play around: http://jsfiddle.net/resting/2yr0b6v7/
So I want to position the word eye over the eye of the cat, regardless of window size.
CSS or JS solutions are both welcomed.
EDIT: Added js alternative
I was convinced that this could be done with css and almost gave up, but then I remembered the new(ish) css units vh and vw....
jsfiddle
CSS
html, body{
height: 100%;
width: 100%;
}
.cat {
height: 100%;
width: 100%;
position: relative;
background:url(http://placekitten.com/g/800/400) no-repeat center center / cover;
}
.place-on-eye {
position: absolute;
color: #fff;
margin:0;
}
#media (min-aspect-ratio: 2/1) {
.place-on-eye {
bottom: 50%;
left: 46.875%;
margin-bottom: 1.25vw;
}
}
#media (max-aspect-ratio: 2/1) {
.place-on-eye {
bottom: 52.5%;
left: 50%;
margin-left: -6.25vh;
}
}
Explanation
So the left eye is at approx 375, 190, and since the image is centered, we will also want to know how far off the center it is, so 25, 10. Since the image is covering, the size of the image will change based on whether the aspect ratio of the viewport is greater or less than the aspect ratio of the background image. Knowing this, we can use media queries to position the text.
The image is 2:1, so when the viewport aspect ratio is > 2:1, we know that the width of the image is the width of the viewport, so the left position of the <p> should always be 46.867% (375/800). The bottom position is going to be more difficult because the image extends beyond the viewport top and bottom. We know that the image is centered, so first move the <p> to the middle, then push it up by 2.5% (10/400) of the height of the image. We don't know the height of the image, but we do know the image aspect ratio and that the width of the image is equal to the width of the viewport, so 2.5% of the height = 1.25% width. So we have to move the bottom up by 1.25% width, which we can do by setting margin-bottom:1.25vw. Incidentally, we can do this without vw in this case because padding is always calculated relative to the width, so we could have set padding-bottom:1.25%, however this won't work in the next case where you have to position the left relative to the height.
The case when the aspect ratio is < 2:1 is analogous. The height of the image is the height of the viewport, so the bottom position should always be 52.5% (210/400) and the left is calculated similar to above. Move it over to center, then back it up by 3.125% (25/800) the width of the image, which is equal to 6.25% the height of the image, which is equal to the viewport height, so margin-left:-6.25vh.
Hopefully this is correct and helps you out!
JS Alternative
jsfiddle
Here's an alternative that uses js. It uses some features like forEach and bind that might cause problems depending on how old a browser you need it to work on, but they are easily replaceable. With js you can directly calculate the scaled dimensions of the bg image which makes the positioning easier. Not the most elegant code, but here goes:
//elem: element that has the bg image
//features: array of features to mark on the image
//bgWidth: intrinsic width of background image
//bgHeight: intrinsic height of background image
function FeatureImage(elem, features, bgWidth, bgHeight) {
this.ratio = bgWidth / bgHeight; //aspect ratio of bg image
this.element = elem;
this.features = features;
var feature, p;
for (var i = 0; i < features.length; i++) {
feature = features[i];
feature.left = feature.x / bgWidth; //percent from the left edge of bg image the feature resides
feature.bottom = (bgHeight - feature.y) / bgHeight; //percent from bottom edge of bg image that feature resides
feature.p = this.createMarker(feature.name);
}
window.addEventListener("resize", this.setFeaturePositions.bind(this));
this.setFeaturePositions(); //initialize the <p> positions
}
FeatureImage.prototype.createMarker = function(name) {
var p = document.createElement("p"); //the <p> that acts as the feature marker
p.className = "featureTag";
p.innerHTML = name;
this.element.appendChild(p);
return p
}
FeatureImage.prototype.setFeaturePositions = function () {
var eratio = this.element.clientWidth / this.element.clientHeight; //calc the current container aspect ratio
if (eratio > this.ratio) { // width of scaled bg image is equal to width of container
this.scaledHeight = this.element.clientWidth / this.ratio; // pre calc the scaled height of bg image
this.scaledDY = (this.scaledHeight - this.element.clientHeight) / 2; // pre calc the amount of the image that is outside the bottom of the container
this.features.forEach(this.setWide, this); // set the position of each feature marker
}
else { // height of scaled bg image is equal to height of container
this.scaledWidth = this.element.clientHeight * this.ratio; // pre calc the scaled width of bg image
this.scaledDX = (this.scaledWidth - this.element.clientWidth) / 2; // pre calc the amount of the image that is outside the left of the container
this.features.forEach(this.setTall, this); // set the position of each feature marker
}
}
FeatureImage.prototype.setWide = function (feature) {
feature.p.style.left = feature.left * this.element.clientWidth + "px";
feature.p.style.bottom = this.scaledHeight * feature.bottom - this.scaledDY + "px"; // calc the pixels above the bottom edge of the image - the amount below the container
}
FeatureImage.prototype.setTall = function (feature) {
feature.p.style.bottom = feature.bottom * this.element.clientHeight + "px";
feature.p.style.left = this.scaledWidth * feature.left - this.scaledDX + "px"; // calc the pixels to the right of the left edge of image - the amount left of the container
}
var features = [
{
x: 375,
y: 190,
name: "right eye"
},
{
x: 495,
y: 175,
name: "left eye"
},
{
x: 445,
y: 255,
name: "nose"
},
{
x: 260,
y: 45,
name: "right ear"
},
{
x: 540,
y: 20,
name: "left ear"
}
];
var x = new FeatureImage(document.getElementsByClassName("cat")[0], features, 800, 400);
I have done a fiddle borrowing the principles from the 2 answers. Black dot should overlay at the end of the line. But this solution drifts from actual spot a little in certain ratios.
Maybe someone can improve it?
JS:
$(function() {
function position_spot() {
w = $(window).width();
h = $(window).height();
wR = w/h;
// Point to place overlay based on 1397x1300 size
mT = 293;
mL = -195;
imgW = 1397;
imgH = 1300;
imgR = imgW/imgH;
tR = mT / imgH; // Top ratio
lR = mL / imgW; // Left ratio
wWr = w / imgW; // window width ratio to image
wHr = h / imgH; // window height ratio to image
if (wR > imgR) {
// backgroundimage size
h = imgH * wWr;
w = imgW * wWr;
} else {
h = imgH * wHr;
w = imgW * wHr;
}
$('.overlay-spot').css({
'margin-top': h * tR,
'margin-left': w * lR
});
}
$(window).resize(function() {
position_spot();
});
position_spot();
});
According to how you set your background image position and size:
background-position:center center;
background-size:cover;
the center of background image should still be in the center of your screen - that comes in handy as a constant, so just try to do the same with your p.place-on-eye
.place-on-eye {
...
top: 50%;
left: 50%;
}
Right now paragraph's left top corner is in the center of your screen, if you also add width and height properties you can actually pint elements center into the screen's center. So it's like:
.place-on-eye {
...
width:50px;
height:50px;
text-align:center /* to make sure the text is center according to elements width */
top: 50%;
left: 50%;
margin:-25px 0 0 -25px;
}
So now the center of p.place-on-eye is in the exact center of your screen, just like the center of your background image. To get it over the cat's eye just offset the left and top margin as needed.
so something like margin:-27px 0 0 -60px; should do it.
fiddle
I have a map view page in my project which contains google map.In my full view, map container takes 70% (width) and 30% (width) of side container which is the list view corresponding to the map markers.Side container having an arrow button in the right corner, whenever I click on the button, side container should hide and map container should show in the full view (from 70% to 100%) without moving the map.
You need to take three steps.
Resize the map container with JavaScript
Calculate the pixel offset of the map center and apply it to the map
Trigger the resize event to load tiles that were not displayed before
A working example follows (see it live on JSFiddle).
HTML
<div id="map"></div>
<button id="toggle">Toggle</button>
CSS
#map {
width: 70%;
height: ...
}
JavaScript
var state = 0;
$(document).ready(function() {
var map = new google.maps.Map(document.getElementById('map'), {
center: new google.maps.LatLng(52.5167, 13.3833),
zoom: 11
});
$('#toggle').click(function() {
var width, offste;
if ((state++ % 2) == 0) {
width = 100;
offset = $('#map').outerWidth() * (1.0 - 1.0 / 0.7) / 2.0 ;
} else {
width = 70;
offset = $('#map').outerWidth() * (1.0 - 0.7) / 2.0 ;
}
$('#map').css('width', width + '%');
map.panBy(offset, 0);
google.maps.event.trigger(map, 'resize');
});
});
Note that you may need to adapt the width and offset calculation to your particular case.
I have a problem with a JavaScript I'm developing for my website. I have images which opens on hovering over them.
First my script calculates if the image should be displayed on the right or on the left of my window:
$("html,body").live("mousemove", function (e) {
//console.log("mousemove: "+e.pageX)
var width_window = $(window).width();
var center = width_window / 2;
if (e.pageX < center) {
side = 'left';
} else {
side = 'right';
}
});
Then, once we know on which side of the window the image will be displayed, I have another script to resize the image, depending of the height & width of my window, including the margins:
this.resizeImg = function (img, offset) {
var d = new Date();
//console.log(d, side);
var window_height = $(window).height();
var img_height = $(img).height();
var img_top = $(img).offset().top;
var window_width = $(window).width();
var img_width = $(img).width();
var img_left;
side == 'left' ? img_left = offset.left : img_left = window_width - offset.left;
console.log(window_width, img_left)
var image_resize_height = window_height - img_top - 20;
var image_resize_width = window_width - img_left - 20;
if (img_height + img_top > window_height && img_width + img_left > window_width) {
console.log("h w")
if (image_resize_width > image_resize_height) {
$(img).css('height', image_resize_height + 'px').css("width", "auto");
} else {
$(img).css('width', image_resize_width + 'px').css("height", "auto");
}
} else if (img_height + img_top > window_height) {
//console.log("h")
$(img).css('height', image_resize_height + 'px').css("width", "auto");
} else if (img_width + img_left > window_width) {
//console.log("w")
$(img).css('width', image_resize_width + 'px').css("height", "auto");
} else {
//console.log("non")
}
};
It almost works, but sometimes my images exceed the window width or height. I can't find the solution...
Here is my CSS:
.vignette {
max-height: 800px;
max-width : 800px;
z-index : 2;
top : 25px;
}
.info{
position : relative;
}
.info img {
position : absolute;
display : none;
cursor : pointer;
}
My full script in jsFiddle: http://jsfiddle.net/CrnNZ/
Here is the link to my website : http://olivierlellouche.com/
Thanks a lot for your help !
Are you taking care of the fact that you are moving the image down 25px in:
.vignette {
top : 25px;
}
The only height adjustment I see is 20px:
var image_resize_height = window_height - img_top - 20;
You may just need to subtract few more pixels to your calculations?
Or better yet:
var img_top = $(img).offset().top;
May be top of the offset area and not the raw top of the image. In which case, you still need to subtract 25px for that.
(From your website) The other thing that may be useful is to always enable, or always disable the vertical scroll-bar on the right. Or re-size the text area to be smaller than the available area when their isn't a scroll-bar. (Unfortunately, I could not get your jsfiddle to work at all and the only error from their I could view was vertical calculation errors. I could not see any horizontal errors.)
Does the problem continue if you subtract a few more pixels off the height?
I can't tell from your code but, does it place the image then re-size it? It may be better idea to calculate the size available before trying to place the image, that way it never changes sizes once it is placed.
EDIT:
After looking at your webpage with much smaller sized window I thought of something else. $(window).height() is not the same as $(document).height(). See: $(window).height() vs $(document).height You may need to calculate the remaining page differently if they are not the same.
I'm working on a web app where a large number of thumbnails will be displayed. I would like to achieve the behavior used in iTunes when displaying albums as thumbnails in a grid (not Coverflow). The idea is that the thumbnails have a fixed size, while the container div has a fluid width. As many thumbnails as possible should be fit in one row, and the margin between the thumbnails should be adaptive so that the thumbnails always take up 100% width of the container.
See the two images below:
Four thumbnails making use of the full width
A slightly smaller window where three thumbnails fit, still taking up the full width by increasing the margin
If it is possible to achieve this with CSS, that is preferable, otherwise I would appreciate JavaScript/jQuery solutions as well.
As promised i have coded this up for you. It uses the following jquery, to evaluate the margin required:
var thewidth = 0;
$('.album').each(function(){
thewidth = thewidth + $(this).width();
});
var numberofdivs = $('#coolio').width() / $('.album').width();
numberofdivs = Math.floor(numberofdivs).toFixed(0)
if (numberofdivs >= $('.album').size()){
numberofdivs = $('.album').size();
};
var widthleft = $('#coolio').width() - ($('.album').width() * numberofdivs);
var margin = (widthleft / numberofdivs) / 2;
$('.album').attr('style', 'margin-left:'+margin+'px; margin-right:'+margin+'px');
http://jsfiddle.net/ajthomascouk/dMAdm/
Hit run first. :)
we can do it use css, here the code http://jsfiddle.net/yuliantoadi/SMNWt/, is that what you mean?
CSS:
.dsgnPgs { margin-top:25px; }
.dsgnPgs li { float:left; width:130px; height:130px; margin-left:10px; margin-bottom:10px; border:1px solid #a6a6a6; background:#e6e6e6; }
.dsgnPgs li:hover { background:#fff; }
.dsgnPgs li h2 { margin:0; padding:0; text-align:center; border:none; }
.dsgnPgs li h2 a { display:block; width:80%; height:70%; padding:20% 10% 10%; }
HTML:
<ul class="dsgnPgs">
<li><h2>test</h2></li>
<li><h2>Catalog</h2></li>
..
</ul>
Made some small improvements to the script given by Alex, to fully suit my needs. Now the first thumbnail on each row doesn't get a left-margin, and the last thumbnail on each row doesn't get a right-margin, so that the thumbnails really make us of the full width of the container div. Also used jQuery's .outerWidth() instead of .width() to retrieve the width of the thumbnail, so that you can use borders etc. without compromising the calculation. Now the script also run as soon as the DOM is loaded, to calculate the proper margin from the beginning, and not only when the window is re-sized.
Here's the new script:
<script type="text/javascript">
$(document).ready(calculateThumbnailMargin);
$(window).resize(calculateThumbnailMargin);
function calculateThumbnailMargin() {
// Define a minimum margin
var minimumMargin = 20;
// Get the outer width of the thumbnail (including padding and border)
var thumbWidth = $('.video-thumbnail-container').outerWidth();
// Calculate how man thumbnails can fit on one row
var numberofdivs = $('#content-area').width() / thumbWidth;
numberofdivs = Math.floor(numberofdivs).toFixed(0);
if (numberofdivs >= $('.video-thumbnail-container').size()) {
numberofdivs = $('.video-thumbnail-container').size();
};
// Calculate the remaining width of the row
var widthleft = $('#content-area').width() - (thumbWidth * numberofdivs);
// Calculate the proper margin to use
var margin = (widthleft / (numberofdivs - 1)) / 2;
// Check that the margin is not less than the minimum margin
if (margin < minimumMargin) {
// Use one less thumnail on each row
numberofdivs = numberofdivs - 1;
// Calculate the remaining width of the row
widthleft = $('#content-area').width() - (thumbWidth * (numberofdivs));
// Calculate the proper margin to use
margin = (widthleft / (numberofdivs - 1)) / 2;
}
// Add the proper margin to each thumbnail
$('.video-thumbnail-container').attr('style', 'margin-left:' + margin + 'px; margin-right:' + margin + 'px');
// Remove the left-margin on the first thumbnail on each row and the right-margin on the last thumbnail on each row
for (i = 0; i < $('.video-thumbnail-container').size(); i = i+numberofdivs) {
$(".video-thumbnail-container:eq(" + i + ")").css('marginLeft', 0);
$(".video-thumbnail-container:eq(" + (i + numberofdivs - 1) + ")").css('marginRight', 0);
}
}