fit images with different aspect ratios into multiple rows evenly - javascript

Good morning.
First, thanks in advance! I've been a stack overflow spectator for quite a while, and you guys are great.
I am looking to create a photo layout for my webpage www.eden-koru.com, where photos are presented in rows. Due to cropping, and different cameras, each photo may have different aspect ratios and therefor there are many uneven gaps when just placed in a row.
A perfect example of what I want to do is www.flickr.com/childe-roland. Those are my photos, all laid out perfectly despite aspect ratio.
On a different, but similar question I found an 80% solution with this JSFiddle http://jsfiddle.net/martinschaer/aJtdb/:
var container_width = $('#container2').width();
var container_width_temp = 0.0; // must be float!
var container_height = 100.0; // random initial container heigth for calculations
$('#container2 img').each(function(){
var newwidth = (this.width / this.height) * container_height;
this.width = newwidth;
$(this).data('width', newwidth);
container_width_temp += newwidth;
});
$('#container2 img').each(function(){
this.width = $(this).data('width') * (container_width / container_width_temp);
});
Now, that only works for one row. I have no experience with JQuery, but I was able to see the math and create a "row_counter" variable that counted the number of image wrapper divs... That got me to 90%. I just multiplied the final width by that row count, then subtracted a few pixels to make up for margins.
it looks like this:
$('.imageWrapper').each(function(){
rows +=1;
});
My div layout looks like this:
<div class="mainWrapper">
<div class="imageWrapper">
<img width="326" src="images/_DSC4434.jpg"></img>
<img width="276" src="images/_DSC4537.jpg"></img>
<img width="254" src="images/_DSC4483.jpg"></img>
</div>
<div class="imageWrapper">
<img width="276" src="images/_DSC0253.jpg"></img>
<img width="306" src="images/The_Alaska_RangeIR.jpg"></img>
<img width="275" src="images/DSC_9111.jpg"></img>
</div>
<div class="imageWrapper">
<img width="276" src="images/_DSC4689.jpg"></img>
<img width="276" src="images/_DSC4718.jpg"></img>
<img width="276" src="images/_DSC4738.jpg"></img>
</div>
</div>
and my CSS like this:
.mainWrapper {
background-color: black;
margin: 0 auto 50px auto;
width: 70%;
height: auto;
border: 2px solid white;
border-radius: 10px;
clear: both;
padding: 7px 7px 7px 7px;
text-align: center;
overflow: hidden;
}
.mainWrapper .imageWrapper {
overflow: hidden;
width: 100%x;
margin: 0px auto 0px auto;
}
.mainWrapper .imageWrapper img {
display: inline-block;
border: 1px solid #fff;
}
Now, it looks better than it did, but there is still a lot of unevenness that I can't account for with styling. Additionally I can no longer use width: 100% to make my images shrink as the viewport changes.
I hope that I have given enough detail. Please keep in mind that I know nothing about JQuery and haven't touched JavaScript in 5 years. I was an IT major who joined the navy after graduation and never coded again until last week.
Cheers!
Wes

This is something quite complex. I managed to make a jQuery plugin that almost achieves what you want, I'm having some issues with making it dynamic when a user resizes their browser window, but ignoring this, it should do what you're asking for.
jQuery Plugin
(function ( $ ) {
$.fn.gallery = function( options ) {
var settings = $.extend({
imgs: [],
row_height: 300,
margin: 10
}, options);
var container = $(this);
//create a div for each image
for(var i=0;i<settings.imgs.length;i++){
$(this).append("<div class='imgwrapper'></div>");
}
//setup the css for the imgwrappers
$("head").append("<style type='text/css'>.imgwrapper{ float: left; margin-left: "+settings.margin+"px; margin-top: "+settings.margin+"px; height: 261px; background-repeat: no-repeat; background-position: center; background-size: cover;}</style>")
//define some global vars
var imgs_aspect = [];
var imgs_rows = [0];
var tot = 0;
var loaded = 0;
function setup(){
var imgs = settings.imgs;
var row_width = 0;
$(".imgwrapper").each(function(index){
var imgwrapper = $(this);
var img = new Image();
img.src = imgs[index];
img.onload = function(){
//determine the aspect ratio of the image
var img_aspect = img.height/img.width;
imgs_aspect.push(img_aspect);
//work out a rough width for the image
var w = settings.row_height*img_aspect;
row_width += w;
//check if there is still space on this row for another image
if(row_width >= container.width()){
imgs_rows.push(1);
row_width = 0;
}
else{
imgs_rows[imgs_rows.length-1]++;
}
//set some of the css vars
imgwrapper.css("width",w+"px");
imgwrapper.css("height",settings.row_height+"px");
imgwrapper.css("background-image","url("+imgs[index]+")");
loaded++;
checkIfLoaded();
}
});
}
function checkIfLoaded(){
//make sure all images are loaded
if(loaded == $(".imgwrapper").length){
setHeight();
}
}
function setHeight(){
for(var r=0;r<imgs_rows.length;r++){
if(r==0){
var y = 0;
}
else{
var y = 0;
for(var j=0;j<r;j++){
y += imgs_rows[j]
}
}
if(imgs_rows[r] == 0){
}
else{
tot = 0;
for(var i=y;i<(y+imgs_rows[r]);i++){
tot += imgs_aspect[i];
}
//work out optimum height of image to fit perfectly on the row
var h = ((container.width()-(settings.margin*(imgs_rows[r]+1)))/tot);
$(".imgwrapper").each(function(index){
if(index >= y && index < (y+imgs_rows[r])){
//work out width using height
var w = h*imgs_aspect[index];
$(this).css("width",w+"px");
}
});
}
}
}
setup();
};
}( jQuery ));
How To Use
var images = ["http://lorempixel.com/300/300",
"http://lorempixel.com/250/250",
"http://lorempixel.com/200/200",
"http://lorempixel.com/210/220",
"http://lorempixel.com/210/230",
"http://lorempixel.com/260/230",
"http://lorempixel.com/410/830",
"http://lorempixel.com/300/200",
"http://lorempixel.com/250/250",
"http://lorempixel.com/200/200",
"http://lorempixel.com/210/220",
"http://lorempixel.com/210/230",
"http://lorempixel.com/260/230",
"http://lorempixel.com/410/830"];
$(".container").gallery({imgs:images, margin: 0, row_height: 300});
images is an array which should contain the images url that you wish to use. The container can have any width desired (define in the css). The margin value allows you to have a similar white border around the images. Since I saw in your code that you had a row height, this is also implemented, by just changing the row_height value.
Demo: http://codepen.io/motorlatitude/pen/iHgCx
This is far from perfect, but it might give you an idea of what you need to do.
Hope it helps!

Related

JavaScript: Enlarge and decrease image on scroll

I am trying to resize an image on scroll in javascript with the following conditions:
The image is close to an upper and lower image that should not resize
The image has a minimum height of 0, and a max height of almost all screen height
The image should have height 0 until the lower image is fully seen on screen
The image should be resized, letting the lower image's position fixed at the bottom, until the upper image reaches the top.
The image will then start shrinking, and the upper image should be kept at top until the middle image reaches it's minimum size again.
My approach is as follows:
var upperImg = document.getElementById("scroll-sup");
var middleImg = document.getElementById("scroll-int");
var lowerImg = document.getElementById("scroll-inf");
const minHeight = 0;
const maxHeight = document.documentElement.clientHeight - 50;
document.addEventListener("wheel", function(e) {
var upperRect = upperImg.getBoundingClientRect();
var lowerRect = lowerImg.getBoundingClientRect();
var upperDistanceToTop = window.pageYOffset + upperRect.top - 100;
var delta = Math.floor(e.deltaY);
if (lowerRect.y > maxHeight + 500 || middleImg.height < minHeight) {
middleImg.height = minHeight;
} else if (middleImg.height > maxHeight) {
middleImg.height = maxHeight;
} else if (upperRect.top < 60 && middleImg.height > minHeight) {
middleImg.height -= delta;
window.scrollBy(0, -delta);
} else {
middleImg.height += delta;
}
e.preventDefault();
});
.flex-container {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.scroll-img {
border-radius: 0px;
width: 100%;
}
<div style="height: 1000px">some HTML</div>
<div class="flex-container">
<img
src="https://raw.githubusercontent.com/sandraparraga/surveillancepower/main/assets/marco_teorico/scroll-sup.png"
id="scroll-sup"
class="scroll-img"
/>
<img
src="https://raw.githubusercontent.com/sandraparraga/surveillancepower/main/assets/marco_teorico/scroll-int.png"
id="scroll-int"
class="scroll-img"
style="position: sticky; object-fit: fill"
/>
<img
src="https://raw.githubusercontent.com/sandraparraga/surveillancepower/main/assets/marco_teorico/scroll-inf.png"
id="scroll-inf"
class="scroll-img"
/>
</div>
<div style="height: 1000px">some more HTML</div>
I fail to see how my code is wrong, but it is not decreasing the image and keeping the upper one in a fixed position.
Any help is appreciated, thanks in advance.

How to condense JavaScript Using Loops

I have the following code working properly to responsively lazy load background images for a number of divs on a page:
// get frames
// REFACTOR LIST:
var frame1 = document.getElementById('frame1');
var frame2 = document.getElementById('frame2');
var frame3 = document.getElementById('frame3');
var frame4 = document.getElementById('frame4');
var frame5 = document.getElementById('frame5');
// create Lazy loader
var myLazyLoad = new LazyLoad({
elements_selector: ".lazy"
});
// load images responsively
function loadImgs() {
console.log('Loading images...');
if(window.matchMedia("only screen and (max-width:700px)").matches) {
// viewport is less than or equal to 700 pixels wide
// REFACTOR LIST:
var src1 = frame1.getAttribute('data-src-small');
var src2 = frame2.getAttribute('data-src-small');
var src3 = frame3.getAttribute('data-src-small');
var src4 = frame4.getAttribute('data-src-small');
var src5 = frame5.getAttribute('data-src-small');
} else {
// viewport is greater than 700 pixels wide
// REFACTOR LIST:
var src1 = frame1.getAttribute('data-src-large');
var src2 = frame2.getAttribute('data-src-large');
var src3 = frame3.getAttribute('data-src-large');
var src4 = frame4.getAttribute('data-src-large');
var src5 = frame5.getAttribute('data-src-large');
}
// set data-src for lazy loader
// REFACTOR LIST:
frame1.setAttribute('data-src', src1);
frame2.setAttribute('data-src', src2);
frame3.setAttribute('data-src', src3);
frame4.setAttribute('data-src', src4);
frame5.setAttribute('data-src', src5);
// tell lazy loader that the data should be re-processed
// REFACTOR LIST:
frame1.removeAttribute('data-was-processed');
frame2.removeAttribute('data-was-processed');
frame3.removeAttribute('data-was-processed');
frame4.removeAttribute('data-was-processed');
frame5.removeAttribute('data-was-processed');
// tell lazy loader to update
myLazyLoad.update();
}
// load images initially
loadImgs();
// reload images when window is resized across the 700px breakpoint
var lastWindowSize = window.innerWidth;
window.onresize = function(event) {
var currentWindowSize = window.innerWidth;
if((lastWindowSize <= 700 && currentWindowSize > 700) || (lastWindowSize > 700 && currentWindowSize <= 700)) {
loadImgs();
}
lastWindowSize = currentWindowSize;
};
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
&:focus {
outline: none;
}
}
* {
font-family: monaco, courier;
}
body {
margin: 0;
padding: 0;
}
.wrapper {
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center ;
background: #ddd;
}
p {
position: absolute;
top: 0;
left: 0;
margin: 0;
padding: 8px;
color: darkslategray;
background: gold;
}
.frame {
width: 80vw;
height: 200px;
margin: 0 0 1rem 0;
padding: 0;
position: relative;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border: 2px solid gold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-lazyload/8.7.1/lazyload.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- https://stackoverflow.com/questions/50431531/lazylaoding-css-background-not-html-img-tags -->
<main class="wrapper">
<a href="#">
<div id="frame1" class="frame lazy"
data-src-small="https://source.unsplash.com/random/400x200?sig=1"
data-src-large="https://source.unsplash.com/random/1200x600?sig=1">
<p>1</p>
</div>
</a>
<a href="#">
<div id="frame2" class="frame lazy"
data-src-small="https://source.unsplash.com/random/400x200?sig=2"
data-src-large="https://source.unsplash.com/random/1200x600?sig=2">
<p>2</p>
</div>
</a>
<a href="#">
<div id="frame3" class="frame lazy"
data-src-small="https://source.unsplash.com/random/400x200?sig=3"
data-src-large="https://source.unsplash.com/random/1200x600?sig=3">
<p>3</p>
</div>
</a>
<a href="#">
<div id="frame4" class="frame lazy"
data-src-small="https://source.unsplash.com/random/400x200?sig=4"
data-src-large="https://source.unsplash.com/random/1200x600?sig=4">
<p>4</p>
</div>
</a>
<a href="#">
<div id="frame5" class="frame lazy"
data-src-small="https://source.unsplash.com/random/400x200?sig=5"
data-src-large="https://source.unsplash.com/random/1200x600?sig=5">
<p>5</p>
</div>
</a>
</main>
CodePen here
But I'd like to refactor the code to dry it up. I'm thinking for loops can be used to replace each of the 5 lists under a REFACTOR LIST: comment. My aim is to enable the code for any unknown number of divs with a class of frame.
To start, as an example, I've attempted to refactor the variable declarations at the beginning with the following loop:
var FramesQuantity = document.getElementsByClassName("frame").length
var frameVariables = [];
function createframeVariables() {
for (var i = 0; i <= FramesQuantity; ++i) {
var frameIndex = 'frame' + i;
console.log("frameIndex: " + frameIndex);
frameVariables[i] = document.getElementById(frameIndex);
}
return frameVariables;
}
createframeVariables();
console.log("frameVariables[0]: " + frameVariables[0]);
but that second console log returns null and I'm not sure if this is the right direction anyway.
Any ideas?
As was suggested, I was able to refactor the code, DRYing it up, by using .forEach with .querySelectorAll which obviated the need to set variables:
// for loop demo
document.querySelectorAll('.frame[data-src-small]').forEach( (frame, index) => {
console.log( "index: " + index);
console.log( "frame.dataset.srcSmall: " + frame.dataset.srcSmall);
console.log( "frame.dataset.srcLarge: " + frame.dataset.srcLarge);
})
// create Lazy loader
var myLazyLoad = new LazyLoad({
elements_selector: ".lazy"
});
// load images responsively
function loadImgs(context) {
console.log('Loading images ' + context);
if(window.matchMedia("only screen and (max-width:700px)").matches) {
// viewport is less than or equal to 700 pixels wide
document.querySelectorAll('.frame[data-src-small]').forEach( (frame, index) => {
var srcSmall = frame.dataset.srcSmall;
// set data-src for lazy loader
frame.setAttribute('data-src', srcSmall);
// tell lazy loader that the data should be re-processed
frame.removeAttribute('data-was-processed');
})
} else {
document.querySelectorAll('.frame[data-src-small]').forEach( (frame, index) => {
// viewport is greater than 700 pixels wide
var srcLarge = frame.dataset.srcLarge;
// set data-src for lazy loader
frame.setAttribute('data-src', srcLarge);
// tell lazy loader that the data should be re-processed
frame.removeAttribute('data-was-processed');
})
}
// tell lazy loader to update
myLazyLoad.update();
}
// load images initially
loadImgs("initially");
// reload images when window is resized across the 700px breakpoint
var lastWindowSize = window.innerWidth;
window.onresize = function(event) {
var currentWindowSize = window.innerWidth;
if((lastWindowSize <= 700 && currentWindowSize > 700) || (lastWindowSize > 700 && currentWindowSize <= 700)) {
loadImgs("on resize across breakpoint");
}
lastWindowSize = currentWindowSize;
};
Forked updated version of the original CodePen here.

How to find offset position with touchmove?

I have a really simple code for dragging/moving an element via touchscreen. The code works fine, the only problem is a common problem, but no matter how I try I cannot fix the code to drag from the touchpoint. Instead every movement starts from the edge of the element regardless of initial touch position.
I have seen a few suggestions but all have failed. I am hoping the smaller simpler code will attract a simple fix.
Here is the JavaScript:
<script>
$("#drag").on('touchmove',function(e){
// This is supposed to be the fix,
// but regardless of positioning
// (relative parent) of the divs,
// it is failing ---
var offset = $(this).offset();
var relX =(e.clientX - offset.left);
// By rights the above is the correct solution. But I
// cannot suss out why it is not working.
var touch = e.originalEvent.touches[0];
var x = touch.clientX
$(this).css({
"-webkit-transform": "translate3d("+ x +"px,"+ 0 +"px,0)"
});
});
</script>
<! --And here is the html -->
<body>
<div id="container" class="container">
<div id='drag' class='drag'>
<!---contents--->
</div>
</div>
CSS
#container {
position:relative;
top: 0%;
left: 0px;
width: 500px;
height: 500px;
background: #ccc;
}
#drag {
position:absolute;
top: 20%;
left: 10px;
width: 150px;
height: 10%;
background: #0a0;
}
Here's the answer, took a re-write and a little help from elsewhere.
But for anybody else who is in the same position one day and comes here for advice, you will find it here:
.container{
position:absolute;
top:0%;
left:0%;
width:500px;
height:100%;
}
.drag{
position:absolute;
top:1%;
left:1%;
width:100%;
height:15%;
border-style: solid; 1px;
border-color: blue;
z-index:1000;
}
HTML.
<div id="container" class="container">
<div id='drag' class='drag'>
<center> ...content drag me... </center>
</div>
</div>
JavaScript.
<script>
document.querySelector(".container").addEventListener("touchmove", function(event) {
event.preventDefault(); // Prevent scrolling of window while touchevent triggerd for element.
});
window.addEventListener('load', function(){
var box = document.getElementById('drag'), // the element or box
bleft=0 , // left position of moving element/box
sx=150, // starting x touch point - half the element width is best.
ds = 0, // distance traveled by touch point
tob = null // Touch object holder
box.addEventListener('touchmove', function(e){
var box = document.getElementById('drag'),
tob = e.changedTouches[0] // reference first touch point for this event
var ds = parseInt(tob.clientX) - sx // calculate dist traveled by touch point
box.style.left = ( (bleft + ds > 400)? 400 : (bleft + ds < -10000000)? -10000000 : bleft + ds ) + 'px' // distance element can travel in X direction
box.addEventListener('touchstart', function(e){
var box = document.getElementById('drag'),
tob = e.changedTouches[0] // reference first touch point
bleft = parseInt(box.style.left) // get left position of box
sx = parseInt(tob.clientX) // get x coord of touch point
}, false) // return null activity or event if none
}, false)
}, false)
</script>

element height() not adding + 200?

I would like to scroll to the end of the container. The element is having new items added via ajax on click, so I am recalculating its height each time I click to load more. The page scrolls fine but I would like to also add the navigation height in order to have the exact pixels. It looks like it is not adding + 224 tho
Html
lorem
New items are added via a click and ajax.
Css
nav {
height: 90px;
}
#container {
margin-top: 104px;
margin-bottom: 120px;
}
Jquery
var page = $("html, body");
var pos = $("#container").height() + 224;
page.animate({scrollTop: pos}, 1000);
I think you want something like this no ?
var page = $("html, body");
console.log($("#t").height());
var pos = $("#t").height() - 500;
console.log(pos);
page.animate({scrollTop: pos}, 1000);
div{
height: 1000px;
width: 50px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="t"></div>
you can use scrollIntoView for last added item
var j=0;
function Add(){
var item=null;
for(var i=0;i<20;i++){
j++
item= $('<div class="item">'+j+'</div>').appendTo("#container")
}
item[0].scrollIntoView()
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input type="button" onclick="Add()" Value="Add" />
<div id="container"></div>

jQuery function that returns when a div touches another div upon scroll

how can I give an alert when one div hovers over another div upon scroll? here is a working example,
http://jsfiddle.net/uprosoft/Ek5Gy/267/
I cant find a jQuery code to go after though in-order to give an alert.
Code:
HTML
<div id="container">
<div id="div1">test</div>
<br>
<div id="div2"> another test</div>
</div>
CSS
#div1{
background: green;
position: fixed;
width: 100%;
}
#div2{
background: yellow;
position: relative;
width: 100%;
margin-top: 100px;
}
#container{
height: 1000px;
}
JQUERY ???
/* what jquery code goes here? to alert when the yellow div touches the green div upon scroll? */
Something like that should work:
$(window).scroll(function() {
var div1 = $("#div1");
var div2 = $("#div2");
var div1_top = div1.offset().top;
var div2_top = div2.offset().top;
var div1_bottom = div1_top + div1.height();
var div2_bottom = div2_top + div2.height();
if (div1_bottom >= div2_top && div1_top < div2_bottom) {
// overlapped
}
});​
DEMO: http://jsfiddle.net/Ek5Gy/280/
I know the question is for Jquery but either way, the same done with vanilla JS
function didDiv1TouchedDiv2() {
var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
// Guard
if (div1 === undefined || div2 === undefined) return;
var div1Rect = div1.getBoundingClientRect();
var div2Rect = div2.getBoundingClientRect();
// We need to add the offsetHeight in order to include padding and border of element and get excact position
return div1Rect.top >= div2Rect.top + div2.offsetHeight;
}
window.addEventListener("scroll", didDiv1TouchedDiv2);

Categories