I'm building a Pinterest type board plugin with jQuery, where I'm having difficulty figuring out how they position their modules.
Below is a snippet of how each element is being placed per their nth-value in the HTML. But this isn't right because Pinterest positions elements that go to the next row underneath the shortest column. Quite like this pen here, http://codepen.io/nikhilkumar80/pen/oxpXVK, but I found it difficult to understand.
function modPosition() {
$this.find(".pinterest-board__mod").each(function( modIndex ) {
$(this).css({
position: "absolute",
left: columnWidth * (modIndex % settings.columns) + "%",
width: columnWidth + "%"
});
// ..........
});
}
modPosition();
Here's my CodePen link, http://codepen.io/joshuawaheed/pen/beeJKq?editors=0010
I'm also having difficulties figuring out how to set the elements top position.
What can I do to make this work? I've put this in a function so that the positioning can run it again on document resize and when a user clicks on a filter option to remove the elements and append the appropriate modules from the appropriate array. The plugin is also set to determine module widths based on the options columns value.
Thank you in advance.
You can implement it in several ways
I suggest you two ways
1) you can use one of the js modules
You can read more about it there
https://designshack.net/articles/css/masonry/
http://www.wtfdiary.com/2012/08/6-amazing-jquery-plugins-to-design.html
2) You can use css rules(flexbox)
You can read more about it there
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
Both of these methods have positive and negative traits
For example fleksboks is not supported by all versions of the browser
But JS is more load the processor
I've got it working. I solved this by:
Creating an array, its lengt equal to the column count.
Storing the height value of modules in the first row in the array.
Looping through each module.
Injecting the smallest array value as css position top for the current module.
Adding the height of the current module with the array item containing the smallest value.
Setting the position left value by dividing 100% with the index of of the smallest value in the array.
Here is the code I wrote, which you can view and fork by following this link
function modPosition() {
var columnsHeight = [/* Length equal to column count */], // This will be recreated on window resize.
columnCount = /* Column count value */;
/* Set CSS position top and left. */
function modCssTopLeft() {
var columnIndex = 0;
$this.find(".pinterest-board__mod").each(function( modIndex ) {
var topPos = 0,
leftPos = 0;
if ( modIndex >= columnCount) {
topPos = Math.min.apply( Math, columnsHeight ); // Get smallest value in array.
leftPos = 100 * columnsHeight.indexOf(topPos) / columnCount; // Set left position based on column count.
columnsHeight[columnsHeight.indexOf(topPos)] += $(this).outerHeight(); // Change arrays smallest value by adding it with current modules height.
}
else {
leftPos = 100 * (modIndex++) / columnCount; // Positioning for the modules in the first row.
}
$(this).css({
position: "absolute",
top: topPos + "px",
left: leftPos + "%"
});
$(this).closest(".pinterest-board__content").css({
height: Math.max.apply( Math, columnsHeight ) // Set height to the modules parent container.
});
});
}
modCssTopLeft();
}
modPosition();
$(window).resize(function() {
modPosition();
});
Related
I am creating a sortable drag and drop piece where users can sort and reorder the grid items as they like. I am placing the items with position absolute to get the animation correct.
Basically, I have a container and there will be n number of elements in a row and I need to place these items at equal distances from each other.
I need to find a reliable way to calculate the left values of each element so that it can be placed correctly.
The following is my current code:
let rowElemsCount= 4;
const arrangeItems = (items,rowElemsCount,container,elemWidth,elemHight) => {
elemHight= elemHight|| elemWidth;
let containerWidth = container.offsetWidth;
let containerHeight = Math.ceil(items.length / rowElemsCount) * elemHight;
container.style.height = containerHeight + "px";
items.forEach((item,i)=>{
let pos = {
x: (i%rowElemsCount) * elemWidth+ "px",
y: Math.floor(i/rowElemsCount) * elemHight+ "px"
};
item.style.cssText = "top:"+pos.y+";left:"+pos.x+";"
item.dataset.index = i;
});
}
I use this method to organize the elements, the calculation for the top is perfect but for the left, it just places the elements one after the other. I need to someway replicate the justify-content: space-between for this.
You're forgetting to calculate the white space between your elements in your calculations. If you add up the width of all the children's containers, then subtract it from the width of the parent you get the leftover whitespace you need to break into fractions.
parent width - children total width = leftover whitespace
if the white space is to only be divided into portions between the children elements, you should then divide that leftover whitespace by children count -1 because the first element doesn't need padding
leftover whitespace / ( children - 1 ) = space between
now your left is just that leftover white plus the left value
item index * ( element width + white space )
Now they should be spaced correctly. You're very close with your current code, and implementing these changes should be pretty easy!
So what I want to happen is that when viewing the Span the text is normal but as you scroll down it starts moving until it looks like such:
Before the effect:
While the effect occurs:
The header is represented by spans for each letter. In the initial state, the top pixel value for each is 0. But the idea as mentioned is that that changes alongside the scroll value.
I wanted to keep track of the scroll position through JS and jQuery and then change the pixel value as needed. But that's what I have been having trouble with. Also making it smooth has been another issue.
Use the mathematical functions sine and cosine, for characters at even and odd indices respectively, as the graphs of the functions move up and down like waves. This will create a smooth effect:
cos(x) == 1 - sin(x), so in a sense, each character will be the "opposite" of the next one to create that scattered look:
function makeContainerWiggleOnScroll(container, speed = 0.01, distance = 4) {
let wiggle = function() {
// y-axis scroll value
var y = window.pageYOffset || document.body.scrollTop;
// make div pseudo-(position:fixed), because setting the position to fixed makes the letters overlap
container.style.marginTop = y + 'px';
for (var i = 0; i < container.children.length; i++) {
var span = container.children[i];
// margin-top = { amplitude of the sine/cosine function (to make it always positive) } + { the sine/cosine function (to make it move up and down }
// cos(x) = 1 - sin(x)
var trigFunc = i % 2 ? Math.cos : Math.sin;
span.style.marginTop = distance + distance * trigFunc(speed * y)/2 + 'px';
}
};
window.addEventListener('scroll', wiggle);
wiggle(); // init
}
makeContainerWiggleOnScroll(document.querySelector('h2'));
body {
height: 500px;
margin-top: 0;
}
span {
display: inline-block;
vertical-align: top;
}
<h2>
<span>H</span><span>e</span><span>a</span><span>d</span><span>e</span><span>r</span>
</h2>
Important styling note: the spans' display must be set to inline-block, so that margin-top works.
Something like this will be the core of your JS functionality:
window.addEventListener('scroll', function(e) {
var scrl = window.scrollY
// Changing the position of elements that we want to go up
document.querySelectorAll('.up').forEach(function(el){
el.style.top = - scrl/30 +'px';
});
// Changing the position of elements that we want to go down
document.querySelectorAll('.down').forEach(function(el){
el.style.top = scrl/30 +'px';
});
});
We're basically listening in on the scroll event, checking how much has the user scrolled and then act upon it by offsetting our spans (which i've classed as up & down)
JSBin Example
Something you can improve on yourself would be making sure that the letters wont go off the page when the user scrolls a lot.
You can do this with simple math calculation, taking in consideration the window's total height and using the current scrollY as a multiplier.
- As RokoC has pointed out there is room for performance improvements.Implement some debouncing or other kinds of limiters
I have a div call it #container,
Inside this #container I have n amount of img tags call it images
n can be 2, 10, 40 and so on.
I am wondering how I can fit n amount of images inside a #container to close all white spaces stretch the images. Quality doesn't matter
This is what I tried until now:
var amount = $("#container > img").length;
var amount_w = amount*200; //200 width of 1 image
var amount_h = amount*130; //120 height image
var refH = $("#container").height();
var refW = $("#container").width();
var refRatio = refW/refH;
$("#container img").each(function(){
$(this).height((amount_h-130)-refH);
$(this).width((amount_w-230)-refW);
});
First of all, it IS possible to achieve what you need even while maintaining the aspect ratio of the images - however the row height will be calculated, but it is not a trivial task (well, at least not as trivial as a single line formula).
There is a jQuery plugin called jPictura which I have developed. I believe the plugin does exactly what you need.
Here is a working fiddle.
You can find the plugin source codes and documentation on GitHub.
Simple example how to use the plugin:
$(document).ready(function () {
$('#my-gallery').jpictura({
layout: { itemSpacing: 0, justifyLastRow: true, idealRowHeight: 200}
});
});
itemSpacing - amount of space between the images in pixels
justifyLastRow - if true, the images in last row will be stretched to take the full width of the row
idealRowHeight - The desired height of the rows in pixels. The plugin will do its best to arrange the items so the row heights are as close as possible to this value.
there are a lot more options documented on GitHub
Beside the JS stuff that calculates the correct widths and heights of the images, there is one more thing to be considered regarding the blank space between images. Images are by default inline-blocks which means they behave like words and words do have some white space inbetween, right? Make them display: block; float: left; or use the flex box layout to get rid of the blank space. The plugin uses float: left; by default.
I created something that might interest you
var container = $('#container');
var height = container.outerHeight();
var width = container.outerWidth();
function populate(n){
var rem_items = n;
var rows = Math.round(Math.sqrt(n));
var row_items = Math.ceil(n/rows);
for (var i=0; i<rows; i++){
// this prevents us from generating a lonely single box in a row
if( (rem_items%(rows-i))===0 ){
row_items = rem_items/(rows-i);
}
if(rem_items<row_items){
row_items = rem_items;
}
rem_items = rem_items-row_items;
for (var j=0; j<row_items; j++){
var img_height = height/rows;
var img_width = width/row_items;
var img_left = j*img_width;
var img_top = i*img_height;
var img = $('<div class="cell"></div>');
img.css({
width: img_width,
height: img_height,
left: img_left,
top: img_top
});
container.append(img);
}
}
}
populate(40);
https://jsfiddle.net/jLq4hgaa/1/
Basically, it calculates the "most balanced" distribution of the images horizontally and vertically.
It does what you're asking for in the plainest sense. It distributes images/containers inside a container evenly regardless of aspect ratio.
$(document).on("pageload",function(){
$('.container').addClass('stretch');
});
Then make a css element called "stretch" defining width:100%
Height:100% and if need be define layout, i.e relative
I have a set of div's generated by php, and they all have the same class. when I click one, it calls a jQuery function that gets its id (defined by MySQL database) and increases its size (height and width) by 110%. The problem i think I'm having, is when I retrieve its css properties by class, it gets the size of the first thing it sees with that class.
theres not much code to show, but its:
var height = parseInt($(".divClass").css("height"));
var width = parseInt($(".divClass").css("width"));
$(".divClass").css({"height":height + "px","width":width + "px"});
$("#" + id).css({"height":height * 1.1 + "px","width":width * 1.1 + "px"});
id is created when the function is called, and is the id of each individual div.
A: Am i even correct about my assumptions? B: How would i go about getting the css properties for the majority of elements with the same property instead of the first one. What is happening is when i click the first element, it, and the rest grow. Any help would be greatly appreciated.
My suggestion would be to do:
var height, width;
$('.divClass').click(function() {
height = $(this).height();
width = $(this).width();
$(this).css({ 'height': ( height * 1.1 ) + 'px', 'width': ( width * 1.1 ) + 'px' });
});
I haven't tested my code by the way so let me know how it works for you and I'll tweak as necessary.
I have a site. I want to make 3 vertical divs with equal height. For this purposes I change the height of last block in each column/div.
For example, the naming of 3 columns are:
.leftCenter
.rightCenter
.right
Now I wrote a code which set the equal height for .leftCenter and .rightCenter:
var left = $('.leftCenter').height();
var center = $('.rightCenter').height();
var news = $('#newItemsList').height();
if (center < left)
$('.rightCenter').height(center + (left-center));
else if (center > left)
$('#newItemsList').height(news + (center-left));
news is the latest subblock in left column (there are 3 images in it). So, if central div is bigger than left div, I change the height of news to make them equal. This code works in Firefox, but doesn't work in Chrome. That's the first question. And the last is: how to make equal 3 divs (including right one).
I needed to make elements equal in height and width so I made the following function that allows you to define a height, or width, or really whatever at it. refType would be used if you sent a min-height and needed it to match the height of the tallest element.
elemsEqual = function (options) {
var defaults = {
'type' : 'height',
'refType' : '',
'elements' : [],
'maxLen' : 450
},
settings = $.extend({}, defaults, options),
max = 0;
$(settings.elements.join(",")).each(function () {
max = Math.max( max, parseInt( $(this).css(settings.type) ) );
if(settings.refType.length) max = Math.max( max, parseInt( $(this).css(settings.refType) ) );
});
max = ((max < settings.maxLen) ? max : settings.maxLen);
$(settings.elements.join(",")).css(settings.type, max + "px");
};
elemsEqual({elements : ['#selector1','#selector2', ... '#selectorN'], type : 'height'});
Well I have this so far:
//Get the height of the right column since it starts at a different Y position than the other two
var right=$('.right').outerHeight(1)-$('.left').children('header').outerHeight(1)-$('.left .innav').outerHeight(1);
//Get the max of the 3
var height_max=Math.max($('.leftCenter').outerHeight(1),$('.rightCenter').outerHeight(), right);
//Apply the max to all 3
$('.rightCenter').height(height_max-3); //-3 to accommodate for padding/margin
$('.right').height(height_max);
$('.leftCenter').height(height_max);
The only problem is that it does not make #newItemsList as tall as the parent, .leftCenter. It also assumes that the right div will be largest, so I don't know if it will still work if it isn't the biggest of the 3.