Transition between colspan changes in Tailwind - css

I have some javascript which will change the col-span-{x} class of a div to correctly give the element its width on the page. I would like this change to be animated over a second instead of an instant jump.
<div class="card col-span-{colSpan} transition duration-1000">...</div>

As per the Tailwind docs, col-span-{number} classes are setting the grid-column CSS property (MDN), namely class names col-span-1 and col-span-2 correspond to grid-column: span 1 / span 1; and grid-column: span 2 / span 2;.
These grid-column shorthand property and its values are not animatable. So, you have to fight the headwind of animating the widths yourself, in a tedious way like this:
clone the animated grid cell element and its next neighbour
add transition: width 1s; to the style of clones
put the clones exactly over their originals using element.getBoundingClientRect() for their pixel position and dimensions
change the col-span-{number} class names in the origin elements as needed
get the pixel position and dimensions of the originals with new col-span-{number} classes
feed new widths to the clone elements
on the transitionend event, remove the clones and show the originals
Deal with the literal edge-cases when you change the col-span-{number} on an edgemost item.
Well, here's a demo that needs refactoring and resembles the good old Masonry layout.
let grid;
const hide = (elem) => {
elem.style.opacity = 0;
}
const show = (elem) => {
elem.style.opacity = 1;
}
const animateWidth = (elem) => {
const gridRect = grid.getBoundingClientRect();
const targetRect = elem.getBoundingClientRect();
const clone = elem.cloneNode();
clone.style.pointerEvents = 'none';
clone.innerHTML = elem.innerHTML + ' clone';
clone.classList.add('clone');
clone.style.position = 'absolute';
clone.style.zIndex = 999;
clone.style.transition = 'width 1s, left 1s, top 1s';
const oldWidth = targetRect.width;
clone.style.width = oldWidth + 'px';
const top = targetRect.y - gridRect.y;
clone.style.top = top ? top + 'px' : 0;
const left = targetRect.x - gridRect.x;
clone.style.left = left ? left + 'px' : 0;
grid.appendChild(clone);
hide(elem);
let nextSibling;
let nextSiblingClone;
if (elem.nextSibling && !elem.nextSibling.classList.contains('clone')) {
nextSibling = elem.nextSibling;
nextSiblingClone = createAndGetNextSiblingClone(elem.nextSibling);
}
elem.classList.toggle('col-span-1');
elem.classList.toggle('col-span-2');
const newWidth = elem.getBoundingClientRect().width;
clone.style.width = newWidth ? newWidth + 'px' : 0;
const newLeft = elem.getBoundingClientRect().x - gridRect.x;
clone.style.left = newLeft ? newLeft + 'px' : 0;
const newTop = elem.getBoundingClientRect().y - gridRect.y;
clone.style.top = newTop ? newTop + 'px' : 0;
if (nextSibling) {
const nextSiblingRect = nextSibling.getBoundingClientRect();
const nextSiblingLeft = nextSiblingRect.x - gridRect.x;
nextSiblingClone.style.left = nextSiblingLeft ? nextSiblingLeft + 'px' : 0;
const nextSiblingTop = nextSiblingRect.y - gridRect.y;
nextSiblingClone.style.top = nextSiblingTop ? nextSiblingTop + 'px' : 0;
}
clone.ontransitionend = (event) => {
show(elem);
if (event && event.target && event.target.parentNode) {
event.target.parentNode.removeChild(event.target);
}
}
}
const createAndGetNextSiblingClone = (elem) => {
const gridRect = grid.getBoundingClientRect();
const targetRect = elem.getBoundingClientRect();
const clone = elem.cloneNode();
clone.innerHTML = elem.innerHTML + ' clone';
clone.classList.add('clone');
clone.style.position = 'absolute';
clone.style.zIndex = 999;
clone.style.transition = 'left 1s, top 1s';
clone.style.pointerEvents = 'none';
clone.style.width = targetRect.width ? targetRect.width + 'px' : 0;
const top = targetRect.y - gridRect.y;
clone.style.top = top ? top + 'px' : 0;
const left = targetRect.x - gridRect.x;
clone.style.left = left ? left + 'px' : 0;
grid.appendChild(clone);
hide(elem);
clone.ontransitionend = (event) => {
show(elem);
if (event && event.target && event.target.parentNode) {
event.target.parentNode.removeChild(event.target);
}
}
return clone;
}
const onGridItemClick = (event) => {
animateWidth(event.target);
}
const displayTheGrid = () => {
grid = document.getElementById('grid');
'lightgreen lightblue #dabaf9 beige #ffbbbb #f9daba #99cefa'
.split(' ')
.forEach((color, index) => {
const gridItem = document.createElement('div');
gridItem.innerHTML = index;
gridItem.style.backgroundColor = color;
grid.appendChild(gridItem);
gridItem.onclick = onGridItemClick;
});
}
window.addEventListener('DOMContentLoaded', displayTheGrid);
* {
box-sizing: border-box;
}
#grid {
display: grid;
grid-template-columns: 20fr 20fr 20fr 20fr 20fr;
grid-gap: 10px;
margin-top: 10px;
position: relative;
}
#grid > div {
border-radius: 6px;
padding: 10px;
background-color: silver;
}
.col-span-1 {
grid-column: span 1 / span 1;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
<!-- https://stackoverflow.com/questions/74571960 -->
<div id="grid"></div>

I don't know if with only transition property applied you can perform an animation on col-span.
Try to use transition-all instead of transition

Related

How to remove Group of CSS2DObject from the scene

I have a dynamic msg_arr array of strings that I want to visualize, and I want to remove the old errs Group of CSS2DObject from the scene before creating the new errors Group.
The problem in my case is that the new CSS2DObjects are added perfectly to the Group, but the reset: this.scene.remove(this.errs); is not working well!
Should I remove and create a new CSS2DRenderer??
Can you please tell me how can I be sure of resetting the whole old Group before creating the new one? thanks in advance.
var camera, scene, renderer, errs;
/**
ErrVisu class
*/
class ErrVisu{
constructor(scene){
this.scene = scene;
}
visuError=(x, y, errs)=>{
const x_mm = y * 0.8 - 8.8;
const y_mm = 2.5;
const z_mm = x * 0.8 - 2.4;
const circle = document.createElement('div');
circle.id = "circle";
circle.innerHTML = "!";
const objectCSS = new THREE.CSS2DObject(circle);
objectCSS.position.set(x_mm, y_mm, z_mm);
objectCSS.name = 'exc_' + x + y;
errs.add(objectCSS);
}
errsIface = (msg_arr) => {
this.scene.remove(this.errs);
this.errs = new THREE.Group();
this.errs.name = "errors";
for (let k in msg_arr) {
let p_xx_yy = msg_arr[k].split("_");
let x = Number(p_xx_yy[1]);
let y = Number(p_xx_yy[2]);
this.visuError(x, y, this.errs);
}
this.scene.add(this.errs);
}
}
/**
Create the scene, camera, renderer
*/
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x21252d);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.x = 1;
camera.position.y = 4;
camera.position.z = 5;
scene.add(camera);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild( labelRenderer.domElement );
controls = new THREE.OrbitControls(camera, labelRenderer.domElement);
errs = new ErrVisu(scene);
let msg_arr_ = [ "p_06_08" ];
errs.errsIface(msg_arr_);
window.addEventListener('resize', onWindowResize);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
labelRenderer.render( scene, camera );
}
init();
animate();
#circle {
color: #ffffff;
background: #ff5500;
width: 30px;
height: 30px;
border-radius: 50%;
text-align: center;
font-size: 25px;
font-weight: bold;
display: inline-block;
animation: blinkingBackground 0.5s infinite;
}
#keyframes blinkingBackground {
0% {
opacity: 1;
}
25% {
opacity: 0.5;
}
50% {
opacity: 0.1;
}
75% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
<script src="https://cdn.jsdelivr.net/npm/three#0.122.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.122.0/examples/js/controls/OrbitControls.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.122.0/examples/js/renderers/CSS2DRenderer.js"></script>
The solution ( source ) was to create an array of CSS2DObjects and to remove the items of that array from the scene rather than combining the Objects in a Group!:
errs.forEach((item) => {
this.scene.remove(item);
});
errs = [];
visuError=(x, y, errs, scene)=>{
const x_mm = y * 0.8 - 8.8;
const y_mm = 2.5;
const z_mm = x * 0.8 - 2.4;
const circle = document.createElement('div');
circle.id = "circle";
circle.innerHTML = "!";
const objectCSS = new CSS2DObject(circle);
objectCSS.position.set(x_mm, y_mm, z_mm);
objectCSS.name = 'exc_' + x + y;
scene.add(objectCSS);
errs.push(objectCSS);
}

How to use CSS transform: scale(); to fit child div to parent div responsively in react?

I am really stuck. My div contains fixed size divs, texts and images with a lot of pixel based positioning. How can I just wrap them in another div and make them scale to fit the wrapper?
This might be very similar: https://css-tricks.com/scaled-proportional-blocks-with-css-and-javascript/
Let's say that some elements have a class of .scaled-wrapper, and such elements have a single child named .scaled-content. Then we have a function applyScaling which takes a single .scaled-wrapper element, and scales its child accordingly:
let scaledWrapper = document.getElementsByClassName('scaled-wrapper')[0];
let applyScaling = scaledWrapper => {
// Get the scaled content, and reset its scaling for an instant
let scaledContent = scaledWrapper.getElementsByClassName('scaled-content')[0];
scaledContent.style.transform = 'scale(1, 1)';
let { width: cw, height: ch } = scaledContent.getBoundingClientRect();
let { width: ww, height: wh } = scaledWrapper.getBoundingClientRect();
let scaleAmtX = ww / cw;
let scaleAmtY = wh / ch;
scaledContent.style.transform = `scale(${scaleAmtX}, ${scaleAmtY})`;
};
applyScaling(scaledWrapper);
// ---- The rest of the code is just for the demonstration ui ----
let change = () => {
let w = parseInt(wInp.value);
let h = parseInt(hInp.value);
if (!isNaN(w)) scaledWrapper.style.width = `${w}px`;
if (!isNaN(h)) scaledWrapper.style.height = `${h}px`;
scaledWrapper.getElementsByClassName('scaled-content')[0].innerHTML = textInp.value;
applyScaling(scaledWrapper);
};
let wInp = document.createElement('input');
wInp.setAttribute('placeholder', 'input parent width in px');
wInp.addEventListener('input', change);
wInp.value = '200';
document.body.appendChild(wInp);
let hInp = document.createElement('input');
hInp.setAttribute('placeholder', 'input parent height in px');
hInp.addEventListener('input', change);
hInp.value = '200';
document.body.appendChild(hInp);
let textInp = document.createElement('input');
textInp.setAttribute('placeholder', 'input text content');
textInp.addEventListener('input', change);
textInp.value = 'abc';
document.body.appendChild(textInp);
.scaled-wrapper {
position: absolute;
left: 10px; top: 30px;
width: 200px; height: 200px;
outline: 1px solid red;
z-index: 1;
}
.scaled-content {
box-sizing: border-box;
display: inline-block;
transform-origin: 0 0;
background-color: #ffd0d0;
z-index: -1;
}
<div class="scaled-wrapper"><div class="scaled-content">abc</div></div>
The main idea is to take the ratio of the size between parent/child, and use that to scale the child accordingly. Note that it's important that the child's transform-origin is the top-left corner (having transform-origin: 50% 50% causes the child to be centered around the parent's top-left corner).
Also note that applyScaling needs to be called if the size of the parent ever changes (for example, if the parent is a percentage size of the window, and the window resizes).
Using #Gershom's snippet, but keeping aspect ratio
let scaledWrapper = document.getElementsByClassName('scaled-wrapper')[0];
let applyScaling = scaledWrapper => {
// Get the scaled content, and reset its scaling for an instant
let scaledContent = scaledWrapper.getElementsByClassName('scaled-content')[0];
scaledContent.style.transform = 'scale(1, 1)';
let { width: cw, height: ch } = scaledContent.getBoundingClientRect();
let { width: ww, height: wh } = scaledWrapper.getBoundingClientRect();
// let scaleAmtX = ww / cw;
// let scaleAmtY = wh / ch;
let scaleAmtX = Math.min(ww / cw, wh / ch);
let scaleAmtY = scaleAmtX;
scaledContent.style.transform = `scale(${scaleAmtX}, ${scaleAmtY})`;
};
applyScaling(scaledWrapper);
// ---- The rest of the code is just for the demonstration ui ----
let change = () => {
let w = parseInt(wInp.value);
let h = parseInt(hInp.value);
if (!isNaN(w)) scaledWrapper.style.width = `${w}px`;
if (!isNaN(h)) scaledWrapper.style.height = `${h}px`;
scaledWrapper.getElementsByClassName('scaled-content')[0].innerHTML = textInp.value;
applyScaling(scaledWrapper);
};
let wInp = document.createElement('input');
wInp.setAttribute('placeholder', 'input parent width in px');
wInp.addEventListener('input', change);
wInp.value = '100';
document.body.appendChild(wInp);
let hInp = document.createElement('input');
hInp.setAttribute('placeholder', 'input parent height in px');
hInp.addEventListener('input', change);
hInp.value = '100';
document.body.appendChild(hInp);
let textInp = document.createElement('input');
textInp.setAttribute('placeholder', 'input text content');
textInp.addEventListener('input', change);
textInp.value = 'abc';
document.body.appendChild(textInp);
.wrapper-wrapper {
box-sizing: border-box;
border: solid 2px blue;
position: relative;
}
.scaled-wrapper {
position: relative;
width: 100px; height: 100px;
outline: 1px solid red;
z-index: 1;
}
.scaled-content {
box-sizing: border-box;
display: inline-block;
transform-origin: 0 0;
background-color: #ffd0d0;
z-index: -1;
}
<div class="wrapper-wrapper">
<div class="scaled-wrapper">
<div class="scaled-content">abc</div>
</div>
</div>
For React you can do it like this:
const applyScaling = (scaledWrapper, scaledContent) => {
scaledContent.style.transform = 'scale(1, 1)';
let { width: cw, height: ch } = scaledContent.getBoundingClientRect();
let { width: ww, height: wh } = scaledWrapper.getBoundingClientRect();
let scaleAmtX = Math.min(ww / cw, wh / ch);
let scaleAmtY = scaleAmtX;
scaledContent.style.transform = `scale(${scaleAmtX}, ${scaleAmtY})`;
};
const App = ()=>{
const scaledWrapper=useRef();
const scaledContent=useRef();
useEffect(()=>{
if (scaledWrapper.current && scaledContent.current) {
applyScaling(scaledWrapper.current, scaledContent.current);
}
},[scaledWrapper.current,scaledContent.current]);
return(
<div ref={scaledWrapper}>
<div ref={scaledContent}></div>
</div>
);
}
scale() is relative to the size of the current container.
height and width are relative to the size of the parent container.
In this case, I would simply set both height and width to 100% to fill the parent DOM element. If you must use scale - try 100%. After setting height and width.

Facebook/Twitter Style photos grid style layout [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 6 years ago.
Improve this question
I'm trying to implement a facebook style photos grid layout. I'm using angularjs and bootstrap for this. I have come across certain plugins like angular-masonry which I think can be used for this. Following are some snapshots of what I'm actually trying to achieve following possible layouts:
Any idea how can this be achieved ? Is there any other plugin which can make life easy ?
All theses are your possibilities:
ngPhotoGrid
Seamless Responsive Photo Grid
freewall - Using jQuery
Bootstrap Responsive Image Gallery by mobirise
Gamma Gallery
Also read - An AngularJS directive for Masonry
ngPhotoGrid example :
//ngPhotoGrid.js
angular.module("ngPhotoGrid", [])
angular.module("ngPhotoGrid")
.filter("photoUrlSafe", [
"$sce", function($sce) {
return function(text) {
return $sce.trustAsResourceUrl(text);
};
}
])
.directive('endRepeat', function() {
return {
restrict: 'A',
require: "^ngPhotoGrid",
link: function(scope, element, attrs, gridController) {
if (scope.$last) {
gridController.notifyDOMReady(element)
}
}
};
})
.directive("ngPhotoGrid", ["$templateCache", function($templateCache){
$templateCache.put("photo_grid.html",
"<ul class='photo-grid-wrapper' ng-style = 'parentStyle'><li class='grid-cell' ng-repeat= 'image in loadedImages track by $index' ng-style = 'image.cellStyle' ng-click='cellClicked(image)' end-repeat='' ng-attr-data-src='{{image[defaultOptions.urlKey] | photoUrlSafe}}'><img class='grid-cell-image' ng-style = 'image.imageStyle' ng-src='{{image[defaultOptions.urlKey]}}' alt='#'/></li></ul>");
function linker(scope, element, attrs) {
scope.loadedImages = [];
scope.loadedTakenImages = [];
scope.takenImages = [];
// ###OPTIONS
scope.defaultOptions = {
urlKey : "original_url",
sortByKey : "nth",
onClicked : function() {},
onBuilded : function() {},
onDOMReady : function() {},
margin : 2,
maxLength : 5,
isSquare : false,
buildOnLoading : true
}
angular.extend(scope.defaultOptions, scope.gridOptions);
var IS_SQUARE = scope.defaultOptions.isSquare;
var GRID_WIDTH = element.prop('offsetWidth');
var MARGIN = scope.defaultOptions.margin;
if (!GRID_WIDTH) { // set the default width of parent
GRID_WIDTH = 250
}
scope.parentStyle = { width: GRID_WIDTH + "px", overflow: "hidden", position: "relative", margin: 0, padding: 0 }
if(IS_SQUARE) {
scope.parentStyle.height = GRID_WIDTH + "px";
}
var commonStyle = {
display: 'block',
overflow: 'hidden',
cssFloat: 'left',
cursor: 'pointer',
position: 'relative'
};
//callback handler
scope.cellClicked = function(image) {
scope.defaultOptions.onClicked(image);
}
/**
* choose images from the url source to build grid
* take maximum 7 images for best looking
*------------------------------------------------*/
scope.chooseImages = function(images) {
angular.forEach(images, function(image, index) {
var randNumber; //set the id and nth value for image if does not have
randNumber = randomNumber();
image.id = image.id || randNumber;
image[scope.defaultOptions.sortByKey] = image[scope.defaultOptions.sortByKey] || randNumber;
});
var sortedImages = images.sort(function(a, b) {
return a[scope.defaultOptions.sortByKey] - b[scope.defaultOptions.sortByKey]
})
return sortedImages.slice(0, scope.defaultOptions.maxLength)
}
randomNumber = function(max) {
max = max || 999;
return Math.floor(Math.random()*max);
}
scope.preloadImages = function(images) {
scope.takenImages = scope.chooseImages(images)
angular.forEach(scope.takenImages, function(image, index) {
var img;
img = new Image();
img.id = image.id ;
img[scope.defaultOptions.sortByKey] = image[scope.defaultOptions.sortByKey];
img.onload = function(loadedImage) {
scope.loadedTakenImages.push(loadedImage);
// store the original dimesion of image
image.naturalWidth = loadedImage.target.naturalWidth
image.naturalHeight = loadedImage.target.naturalHeight
// build the grid immediatly after the image was loaded
// building while images loading
if(scope.defaultOptions.buildOnLoading) {
scope.buildPhotoGrid();
setTimeout(function() {
scope.$apply()
}, 10)
}
if(scope.loadedTakenImages.length == scope.takenImages.length) {
//trigger build completed handler
scope.defaultOptions.onBuilded(element)
//grid also can be build after all image loaded
//all image would be shown correctly, loading time cause poor UX
if(!scope.defaultOptions.buildOnLoading) {
scope.buildPhotoGrid()
setTimeout(function() {
scope.$apply()
}, 15)
}
}
};
img.src = image[scope.defaultOptions.urlKey];
});
};
scope.buildPhotoGrid = function() {
var firstImage, imageStyle, smallCellHeight,
smallCellWidth, bigCellWidth, bigCellHeight, cellCount, is2First;
// get cell style & builded options
styles = scope.getCellStyles();
smallCellHeight = styles.options.smallCellHeight;
smallCellWidth = styles.options.smallCellWidth;
bigCellWidth = styles.options.bigCellWidth;
bigCellHeight = styles.options.bigCellHeight;
cellCount = styles.options.cellCount;
is2First = styles.options.is2First;
scope.loadedImages = []
angular.forEach(scope.takenImages, function(image, index) {
if (is2First) { //case the grid has 2 image big first
var bigCellStyle, smallCellStyle;
bigCellStyle = angular.copy(styles.big);
smallCellStyle = angular.copy(styles.small);
if (index == 0) {
bigCellStyle.top = "0";
image.cellStyle = bigCellStyle;
image.imageStyle = getImageStyle(bigCellWidth, bigCellHeight, image);
} else if (index == 1) {
bigCellStyle.top = bigCellHeight + MARGIN + "px";
image.cellStyle = bigCellStyle;
image.imageStyle = getImageStyle(bigCellWidth, bigCellHeight, image);
} else {
var margin, smallCellIndex;
// fix the last cell of 2 first was not fit the grid height
if(index == scope.takenImages.length - 1) {
smallCellStyle.height = smallCellHeight + MARGIN + "px"
}
smallCellIndex = index - 2;
margin = smallCellIndex == 0 ? 0 : MARGIN;
smallCellStyle.top = smallCellIndex * smallCellHeight + (margin * smallCellIndex) + "px";
image.cellStyle = smallCellStyle;
image.imageStyle = getImageStyle(smallCellWidth, smallCellHeight, image);
}
} else if (index == 0) { //big cell style
image.cellStyle = styles.big;
image.imageStyle = getImageStyle(bigCellWidth, bigCellHeight, image);
} else if (index != cellCount - 1 || cellCount == 2){ //small cells
image.cellStyle = styles.small;
image.imageStyle = getImageStyle(smallCellWidth, smallCellHeight, image);
} else { //last small cell style (remove redundant margin right or bottom)
image.imageStyle = getImageStyle(smallCellWidth, smallCellHeight, image);
image.cellStyle = styles.last;
}
})
scope.loadedImages = scope.takenImages;
}
function getImageStyle(cellWidth, cellHeight, image) {
var imageWidth, imageHeight, curImageWidth, curImageHeight, imgRatio, cellRatio;
cellWidth = Math.round(cellWidth);
cellHeight = Math.round(cellHeight);
imageWidth = image.naturalWidth;
imageHeight = image.naturalHeight;
imgRatio = imageWidth / imageHeight;
cellRatio = cellWidth / cellHeight;
// when the any image's dimension greater than cell's dimension
if(cellWidth > imageWidth || cellHeight > imageHeight) {
if (cellWidth >= imageWidth) {
return getSmallImagePortraitStyle(cellHeight, cellWidth, imgRatio);
} else {
return getSmallImageLandscapeStyle(cellHeight, cellWidth, imgRatio);
}
} else { // when the image smaller than the cell in both dimension
if(imgRatio >= 1) {
return getSmallImageLandscapeStyle(cellHeight, cellWidth, imgRatio);
} else {
return getSmallImagePortraitStyle(cellHeight, cellWidth, imgRatio);
}
}
}
function getSmallImageLandscapeStyle(cellHeight, cellWidth, imgRatio) {
var curImageWidth = cellWidth;
var curImageHeight = Math.round(curImageWidth / imgRatio);
if(curImageHeight >= cellHeight) {
var top = (-1) * Math.round((cellWidth / imgRatio - cellHeight) / 2);
if(curImageWidth < cellWidth) {
return { width: "100%", position: "relative", top: top + "px"}
} else {
return { maxWidth: "100%", position: "relative", top: top + "px"}
}
} else {
var left = (-1) * Math.round((cellHeight * imgRatio - cellWidth) / 2);
return { maxHeight: "100%", height: "100%", position: "relative", left: left + "px"}
}
}
function getSmallImagePortraitStyle(cellHeight, cellWidth, imgRatio) {
var curImageHeight = cellHeight;
var curImageWidth = Math.round(curImageHeight * imgRatio);
var top = (-1) * Math.round((cellWidth / imgRatio - cellHeight) / 2);
var left = (-1) * Math.round((cellHeight * imgRatio - cellWidth) / 2);
if(curImageWidth <= cellWidth) {
return { width: "100%", position: "relative", top: top + "px"}
} else {
return { maxHeight: "100%", height: "100%", position: "relative", left: left + "px"}
}
}
/**
* build cell style for grid
* #firstRatio : ratio of the first image in list
* #secondRatio : ratio of the second image in list
* #cellCount : total cells in grid
*------------------------------------------------*/
buildCellStyle = function (firstImage, secondImage, cellCount) {
var firstRatio, secondRatio, bigCellStyle, smallCellStyle, lastCellStyle,
WIDTH_RATE, bigCellWidth, bigCellHeight, smallCellHeight, smallCellWidth, is2First,
case2BigImage1, case2BigImage2;
firstRatio = firstImage.naturalWidth / firstImage.naturalHeight;
if (secondImage)
secondRatio = secondImage.naturalWidth / secondImage.naturalHeight;
else
secondRatio = 1.5 //fail all cases below
bigCellStyle = angular.copy(commonStyle);
smallCellStyle = angular.copy(commonStyle);
lastCellStyle = angular.copy(commonStyle);
WIDTH_RATE = getWidthRate(firstRatio, cellCount);
case2BigImage1 = firstRatio > 0.8 && firstRatio < 1.2 &&
secondRatio > 0.8 && secondRatio < 1.2
case2BigImage2 = firstRatio >= 2 && secondRatio >= 2
if(cellCount == 2) { //build style for grid has 2 images and first image has firstRatio > 1
if(firstRatio >= 1) {
bigCellStyle.marginBottom = MARGIN;
bigCellStyle.width = GRID_WIDTH;
bigCellStyle.height = GRID_WIDTH / 2;
smallCellStyle.width = GRID_WIDTH;
smallCellStyle.height = GRID_WIDTH / 2 - MARGIN;
} else {
var marginSize = MARGIN / cellCount;
bigCellStyle.marginRight = marginSize;
smallCellStyle.marginLeft = marginSize;
if(IS_SQUARE) {
bigCellWidth = Math.floor(GRID_WIDTH / 2) - MARGIN;
bigCellStyle.width = bigCellWidth;
bigCellStyle.height = GRID_WIDTH;
smallCellWidth = Math.floor(GRID_WIDTH / 2) - MARGIN;
smallCellStyle.width = smallCellWidth;
smallCellStyle.height = GRID_WIDTH;
} else {
bigCellWidth = Math.floor(GRID_WIDTH * WIDTH_RATE) - MARGIN;
bigCellStyle.width = bigCellWidth;
bigCellStyle.height = bigCellWidth;
smallCellWidth = GRID_WIDTH - bigCellWidth - MARGIN;
smallCellHeight = bigCellWidth;
smallCellStyle.width = smallCellWidth;
smallCellStyle.height = smallCellHeight;
}
}
}
// add style for first column contain 2 big images, only support for grid has more than 5 cells
//NOTE: need check when 2 first were same size!!!
else if (cellCount >= 5 && (case2BigImage1 || case2BigImage2)) {
var GRID_HEIGHT;
WIDTH_RATE = case2BigImage1 ? 1/2 : 2/3;
scope.parentStyle.position = "relative";
bigCellStyle.cssFloat = smallCellStyle.cssFloat = lastCellStyle.cssFloat = null;
bigCellStyle.position = smallCellStyle.position = lastCellStyle.position = "absolute";
//determine the height of the big cell
//height == width / 2 if the grid in case2BigImage1
if(case2BigImage1) {
bigCellHeight = GRID_WIDTH / 2;
} else {
bigCellHeight = WIDTH_RATE * GRID_WIDTH / firstRatio;
}
GRID_HEIGHT = bigCellHeight * 2 + MARGIN; //margin bottom the first big image
scope.parentStyle.height = GRID_HEIGHT + "px";
bigCellStyle.width = GRID_WIDTH * WIDTH_RATE - MARGIN;
bigCellStyle.height = bigCellHeight;
bigCellStyle.left = 0;
smallCellStyle.width = GRID_WIDTH - bigCellStyle.width - MARGIN;
smallCellStyle.height = Math.floor((GRID_HEIGHT / (cellCount - 2))) - MARGIN;
smallCellStyle.right = 0;
is2First = true; //flag this style is has 2 big image style
lastCellStyle.height = smallCellStyle.height + MARGIN;
} else if(firstRatio >= 1) { //build style for grid more than 2 images and first image has firstRatio > 1
bigCellStyle.marginBottom = MARGIN;
smallCellStyle.marginRight = MARGIN;
var smallCellCount = cellCount - 1;
if (IS_SQUARE) {
bigCellStyle.height = GRID_WIDTH * 2 / 3;
bigCellStyle.width = GRID_WIDTH;
smallCellStyle.height = GRID_WIDTH * 1 / 3 - MARGIN;
} else {
bigCellStyle.width = GRID_WIDTH ;
bigCellStyle.height = GRID_WIDTH * 2 / 3;
}
smallCellStyle.width = ( GRID_WIDTH - smallCellCount * MARGIN ) / smallCellCount;
// determine the height of smallCell below
if (IS_SQUARE) {
smallCellStyle.height = GRID_WIDTH - bigCellStyle.height - MARGIN;
} else if (firstRatio > 1.3 && firstRatio < 1.5) { // 4:3 < firstRatio < 5:3
smallCellStyle.height = smallCellStyle.width / firstRatio;
} else if (firstRatio > 1.5) {
smallCellStyle.height = smallCellStyle.width / 1.5;
} else {
smallCellStyle.height = smallCellStyle.width;
}
lastCellStyle.height = smallCellStyle.height;
lastCellStyle.width = smallCellStyle.width;
} else { //build style for grid more than 2 images and first image has firstRatio <= 1
bigCellStyle.marginRight = MARGIN;
smallCellStyle.marginBottom = MARGIN;
if (IS_SQUARE) {
bigCellHeight = GRID_WIDTH;
bigCellWidth = GRID_WIDTH * WIDTH_RATE;
} else {
bigCellWidth = Math.floor(GRID_WIDTH * WIDTH_RATE);
bigCellHeight = bigCellWidth / firstRatio;
}
bigCellStyle.width = bigCellWidth;
bigCellStyle.height = bigCellHeight;
smallCellCount = cellCount - 1;
smallCellWidth = GRID_WIDTH - bigCellWidth - MARGIN;
smallCellHeight = bigCellHeight / smallCellCount - MARGIN
smallCellStyle.width = GRID_WIDTH - bigCellWidth - MARGIN;
smallCellStyle.height = smallCellHeight;
lastCellStyle.width = smallCellWidth;
lastCellStyle.height = smallCellHeight;
}
return {
big: bigCellStyle,
small: smallCellStyle,
last: lastCellStyle,
options: {
firstRatio: firstRatio,
// keep these value because ng style need add measured suffix
smallCellWidth: smallCellStyle.width,
smallCellHeight: smallCellStyle.height,
bigCellWidth: bigCellStyle.width,
bigCellHeight: bigCellStyle.height,
cellCount: cellCount,
is2First: is2First
} //keep these values to style cell image after building style for cell link
}
}
getWidthRate = function(firstRatio, cellCount) {
if (cellCount == 2) { //build style for 2 images
if(firstRatio > 1) {
return 2/3;
} else {
return 1/2;
}
} else if(firstRatio > 1) { //build style for >= 3 images, first image has firstRatio > 1
return 1
} else { //build style for >= 3 images, first image has firstRatio < 1
return 2/3
}
}
scope.getCellStyles = function() {
var firstImage, secondImage, cellCount, buildedStyle;
firstImage = scope.takenImages[0];
secondImage = scope.takenImages[1];
cellCount = scope.takenImages.length;
if (cellCount == 1) { //build style for only one image
//#todo need implement!
} else { //build style for >=2 images
buildedStyle = buildCellStyle(firstImage, secondImage, cellCount);
}
// remove margin right of last small cell in the bottom
if(buildedStyle.small.marginRight) {
buildedStyle.last.marginRight = 0;
buildedStyle.last.width = buildedStyle.small.width + MARGIN;
}
// remove margin bottom of last small cell in the right
if(buildedStyle.small.marginBottom) {
buildedStyle.last.marginBottom = 0;
buildedStyle.last.height = buildedStyle.small.height + MARGIN;
}
// add suffix px for margin and size for ng-style working
var attrs = ["width", "height", "marginRight", "marginLeft", "marginBottom", "left", "right"];
angular.forEach(attrs, function(attr, index) {
if(buildedStyle.big[attr]) {
buildedStyle.big[attr] += "px";
}
if(buildedStyle.small[attr]) {
buildedStyle.small[attr] += "px";
}
if(buildedStyle.last[attr]) {
buildedStyle.last[attr] += "px";
}
})
return buildedStyle;
}
//trigger build grid
scope.$watch("images", function(images) {
if(images && images.length > 0) {
scope.preloadImages(images);
}
})
}
return {
restrict: "A",
templateUrl: "photo_grid.html",
scope: {
images: "=",
gridOptions: "="
},
controller: ["$scope", "$element", function($scope, $element) {
this.notifyDOMReady = function() {
$scope.defaultOptions.onDOMReady($element)
}
}],
link: linker
}
}])
angular.module("ngApp", ["ngPhotoGrid"])
angular.module("ngApp").controller("indexCtrl", ["$scope", function($scope){
//show loading mark while grid is building
$scope.isBuildingGrid = true;
// production test
img1 = {original_url: "http://lorempixel.com/1366/768"};
img2 = {original_url: "http://lorempixel.com/316/316"};
img3 = {original_url: "http://lorempixel.com/400/200"};
img4 = {original_url: "http://lorempixel.com/600/1000"};
img5 = {original_url: "http://lorempixel.com/600/800"};
img6 = {original_url: "http://lorempixel.com/800/600"};
img7 = {original_url: "http://lorempixel.com/800/800"};
img8 = {original_url: "http://lorempixel.com/900/1000"};
// // local dev
// img1 = {original_url: "images/1366x768.jpg"};
// img2 = {original_url: "images/316x316.jpg"};
// img3 = {original_url: "images/600x1000.jpg"};
// img4 = {original_url: "images/900x1000.jpg"};
// img5 = {original_url: "images/600x800.jpg"};
// img6 = {original_url: "images/800x600.jpg"};
// img7 = {original_url: "images/800x800.jpg"};
// img8 = {original_url: "images/900x1000.jpg"};
var sources = [img1, img2, img3, img4, img5, img6, img7, img8]
var sources2big = [{original_url: "http://lorempixel.com/316/316", nth: 1}, {original_url: "http://lorempixel.com/800/800", nth: 2}, img3, img4, img5, img6]
$scope.rand = function(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
$scope.clickHandler = function(image) {
alert(JSON.stringify(image))
}
$scope.buildCompletedHandler = function() {
console.log ("built completed!")
$scope.isBuildingGrid = false;
}
getSelectedSeeds = function() {
var photoNumbers = $scope.rand(2, 7)
var seeds = []
var arr = []
while(arr.length < photoNumbers){
var randomnumber = $scope.rand(1, 8);
var found = false;
for(var i = 0; i < arr.length; i++){
if(arr[i] == randomnumber ){
found = true;
break;
}
}
if(!found) {
arr[arr.length] = randomnumber;
seeds.push(sources[randomnumber])
}
}
return seeds;
}
$scope.images = getSelectedSeeds();
$scope.images2big = sources2big.slice(0, 7);
/**
* Options definitions
*----------------------*/
$scope.gridOptions = {
urlKey : "original_url",
sortKey : "nth",
onClicked : function(image) {
alert(JSON.stringify(image))
},
onBuilded : function() {
console.log ("built completed!")
$scope.isBuildingGrid = false;
},
margin : 2,
maxLength : 5
}
/**
* Options definitions for square example
*----------------------------------------*/
$scope.gridOptionsSquare = {
urlKey : "original_url",
sortKey : "nth",
onClicked : function(image) {
alert(JSON.stringify(image))
},
onBuilded : function() {
console.log ("built completed!")
$scope.isBuildingGrid = false;
},
margin : 2,
isSquare : true,
maxLength : 4
}
$scope.squareGridGroup = [];
angular.forEach([1,2,3,4,5,6], function() {
$scope.squareGridGroup.push(angular.copy(getSelectedSeeds()))
})
}])
/**
* All these styles are not a part of angular module.
*/
.center {
text-align: center;
}
.small {
font-size:12px;
font-weight: normal;
margin-left: 10px;
}
.wrapper {
text-align: center;
}
.content {
width: 400px;
margin: 0 auto;
}
.feed-item{
overflow: hidden;
}
.feed-photos {
position: relative;
min-height: 100px;
}
.feed-photos.loading::after {
content: "";
height: 100%;
width: 100%;
background: rgba(0,0,0,.6) url("loader.gif") center center no-repeat;
left:0;
top:0;
position: absolute;
}
.feed-photos.loading .grid-cell-image{
width: 100%;
}
/**
* It's the part of module
* used to style the border of grid
* included this style if you want to border the grid cell
*/
.grid-cell:after {
content: '';
position: absolute;
border: 1px solid rgba(0, 0, 0, 0.2); /*change this color if you want*/
top: 0;
right: 0;
bottom: 0;
left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<style>
body { text-align: center; }
.links a { padding: 0 5px ;}
.active { color: red;}
</style>
</head>
<body ng-app='ngApp'>
<div class='wrapper' ng-controller="indexCtrl">
<div class="header">
<h1>ngPhotoGrid Example - 2 bigs first</h1>
</div>
<div class='content'>
<div class="feed-item">
<div ng-photo-grid = ""
images = "images2big"
grid-options = "gridOptions"
ng-class = "{'loading': isBuildingGrid}"
class = "feed-photos">
</div>
</div>
<p class='small'><i>click on image or turn on firebug to see the callback.</i></p>
</div>
<div class='footer'>
<p>source on github</p>
</div>
</div>
There is an easier way to do this by using pure css: column-count and column-width.
Here's a fiddle (https://jsfiddle.net/3z73obt0/1/)
And here's the code:
#wrapper {
-moz-column-count: 2;
-webkit-column-count: 2;
column-count: 2;
-moz-column-gap: 10px;
-webkit-column-gap: 10px;
column-gap: 10px;
}
#wrapper > div:nth-child(n+2) {
margin-top:10px;
}
#wrapper > div:nth-child(1) {
height: 150px;
width:100%;
background-color:#8ebce5;
}
#wrapper > div:nth-child(2) {
height: 150px;
width:100%;
background-color:#3a2313;
}
#wrapper > div:nth-child(3) {
height: 100px;
width:100%;
background-color:peru;
}
#wrapper > div:nth-child(4) {
height: 100px;
width:100%;
background-color:#BB3579;
}
#wrapper > div:nth-child(5) {
height: 100px;
width:100%;
background-color:#EAC243;
}
<div id="wrapper">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
If you would still prefer using a library, I would suggest having a look at the isotope library to achieve this. (http://isotope.metafizzy.co/).
Here's a codepen of a nice masonry layout made with isotope: http://codepen.io/desandro/pen/mIkhq
Have you tried ngPhotoGrid? https://github.com/jerryc-nguyen/ng-photo-grid
A simple compact photo grid like Facebook in AngularJS with no dependencies.
From looking at its Examples it looks like what you are looking for.

adding animation to flex-wrap

when it comes to wrap point how can we add animation?
maybe this can help:
we have a header and inside of that header we have container with flex attr and the direction is column when we resize our browser from bottom to top or when we changing height of browser those items suddenly reshape , I just want to add animation to this event.thx
<header>
<div class="container">
<div class="item1 item"></div>
<div class="item2 item"></div>
<div class="item3 item"></div></div></header>
header {
width: 200vw;
max-height: 100vh ;
}
.container{
display: flex;
max-height:100vh;
flex-direction: column;
flex-wrap: wrap;
align-content:flex-start;
}
.item1 {
background-color: red;
height: 200px;
flex: 1 0 150px;
}
.item2 {
background-color: blue;
height: 200px;
flex: 1 0 150px;
}
.item3 {
background-color: orange;
height: 200px;
flex: 1 0 150px;
}
I had a similar need and created a simple utility to achieve it.
- Demo at CodePen: https://codepen.io/hideya/pen/Jamabx
- GH gist: https://gist.github.com/hideya/16ed168a42f74eb5d2162b4e743940ff
The implementation is a bit wild and pretty much assumes no change in flex items except xy coords. You may need to adjust z-index, as it switches item's 'position' to 'absolute'.
Hope this helps.
window.addEventListener('load', function(event) {
var targetClassName = 'flex-wrap-anim';
var defaultDuration = '0.3s';
var dummyList = [];
function addDummy(item, duration) {
var top = item.offsetTop;
var left = item.offsetLeft;
setTimeout(function() {
item.style.position = 'absolute';
item.style.top = top + 'px';
item.style.left = left + 'px';
var dummyDiv = document.createElement('div');
dummyDiv.classList.add(targetClassName + '-dummy');
var rect = item.getBoundingClientRect();
dummyDiv.style.width = rect.width + 'px';
dummyDiv.style.height = rect.height + 'px';
dummyDiv.style.visibility = 'hidden';
dummyDiv['__' + targetClassName + '_pair'] = item;
dummyDiv['__' + targetClassName + '_duration'] = duration;
item.parentNode.appendChild(dummyDiv);
dummyList.push(dummyDiv);
}, 0);
}
var conts = document.getElementsByClassName(targetClassName);
for (var i = 0, max = conts.length; i < max; i++) {
var cont = conts[i];
cont.style.positoin = 'relative';
var duration = cont.getAttribute('data-duration')
|| defaultDuration;
var items = cont.getElementsByTagName('div');
for (var i = 0, max = items.length; i < max; i++) {
addDummy(items[i], duration);
}
}
window.addEventListener('resize', function(event) {
dummyList.forEach(function(dummyDiv) {
var item = dummyDiv['__' + targetClassName + '_pair'];
var duration = dummyDiv['__' + targetClassName + '_duration'];
if (item.offsetTop != dummyDiv.offsetTop) {
item.style.transition = 'all ' + duration;
item.style.top = dummyDiv.offsetTop + 'px';
item.style.left = dummyDiv.offsetLeft + 'px';
} else {
item.style.transition = '';
item.style.left = dummyDiv.offsetLeft + 'px';
}
});
});
});
While this cannot be done with CSS alone, you can accomplish this using JQuery. When looking at a flexbox using rows, the flexbox will change height if a new row is created or removed. Knowing this, we can add a .resize() function to the page to test if a window resize has altered the height of the flexbox. If it has, you can then execute an animation. I have created an example JFiddle here.
Here is the code that makes this work:
$(document).ready(function() {
var height = $('.container').css('height');
var id;
$(window).resize(function() {
clearTimeout(id);
id = setTimeout(doneResizing, 500);
});
function doneResizing() {
var newheight = $('.container').css('height');
if (newheight != height) {
$('.item').fadeOut();
$('.item').fadeIn();
height = newheight;
}
}
});
Now with a flexbox using columns, we need to detect when a change in width occurs. This is slightly more difficult as the flexbox width will take up the maximum allotted width as it is a block style element by default. So to accomplish this, you either need to set it as an inline flexbox using display: inline-flex, or set a maximum width for the flexbox equal to the width of its contents at its largest. Once you have set one of those, you can use the same code as above, except tweaking it to detect changes in width as opposed to height.
These changes applied an animation to all elements on resize. What if you want to only apply it to the element whose row/column changes? This would take more effort but is do-able. You would need to write many if-statements in your javascript/jquery code to catch which flex-item to apply the animation to based on width/height.

css: float blocks to occupy all free space

I'm trying to make an "image mosaic" that consists mostly of images of the same size, and some of them the double height.
They all should align neatly like this:
To make automatic generation of those mosaic as easy as possible, I thought floating them would be the best option. Unfortunately, the big block causes the following ones to flow behind it, but not before:
What can I do - apart from manually positioning them - to get the images to the place I want, and still have it easy to automatically create likewise layouts?
The code I'm currently using is :
FIDDLE
HTML :
<div class="frame">
<div id="p11" class="img">1.1</div>
<div id="p12" class="img h2">1.2</div>
<div id="p13" class="img">1.3</div>
<div id="p21" class="img">2.1</div>
<div id="p22" class="img">2.2</div>
</div>
CSS :
.frame {
background-color: blue;
border: 5px solid black;
width: 670px;
}
.img {
width: 200px;
height: 125px;
background-color: white;
border: 1px solid black;
float: left;
margin: 10px;
}
.h2 {
height: 272px;
}
You need to use Javascript to achieve this effect, I had to do that once and I used http://masonry.desandro.com/ -- worked well!
Pure CSS Solution
Tested in Firefox, IE8+ (IE7 looks like it would need to be targeted to add a top margin added to 2.1 because it overlaps 1.1). See fiddle. This assumes .h2 is the middle div (as your example). If left most div it should not need any change. If right most, you would need to expand the negative margin to also include the third div following.
.h2 + div {
float: right;
margin: 10px 14px 10px 0; /*14px I believe also has to do with borders */
}
.h2 + div + div {
margin-left: -434px; /*need to account for borders*/
clear: right;
}
You can use a column layout like this:
http://jsfiddle.net/KKUZL/
I don't know if that will conflict with your automation process though....
I realize this is not a CSS-only solution, but for what it's worth (JSFiddle):
HTML:
<div id='container'></div>
CSS:
html, body {
margin:0px;
padding:0px;
height:100%;
}
body {
background-color:#def;
}
#container {
margin:0px auto;
width:635px;
min-height:100%;
background-color:#fff;
box-shadow:0px 0px 5px #888;
box-sizing:border-box;
overflow:auto;
}
.widget {
float:left;
box-sizing:border-box;
padding:10px 10px 0px 0px;
}
.widget > div{
height:100%;
box-sizing:border-box;
color:#fff;
font-size:3em;
text-align:center;
padding:.5em;
overflow:hidden;
}
.widget > div:hover {
background-color:purple !important;
}
JS:
////////////////////////////////////////
// ASSUMPTIONS
//
var TWO_COLUMN_WIDGET_COUNT = 1;
var ONE_COLUMN_WIDGET_COUNT = 15;
var NUMBER_OF_COLUMNS = 2;
////////////////////////////////////////
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var colorFactory = (function () {
var colors = [
'#CC9A17',
'#9B2C16',
'#1A8B41',
'#D97114',
'#3B9EE6'];
var index = 0;
return function () {
if (index > 4) {
index = 0;
}
return colors[index++];
}
})();
function widgetFactory(columnSpan) {
return {
'height': rand(10, 30) * 10,
'width': 100 * columnSpan / NUMBER_OF_COLUMNS,
'columnSpan': columnSpan,
'color': colorFactory()
}
}
function getWidgets() {
var widgets = [];
for (var i = 0; i < TWO_COLUMN_WIDGET_COUNT; i++) {
widgets.push(widgetFactory(2));
}
for (var i = 0; i < ONE_COLUMN_WIDGET_COUNT; i++) {
widgets.push(widgetFactory(1));
}
return widgets;
}
function getHighestOffset(offsets){
}
function getHighestSlot(offsets, numOfColumns){
}
$(document).ready(function () {
var container = $('#container');
var widgets = getWidgets();
var col1 = Math.floor(container[0].offsetLeft);
var col2 = Math.floor(container[0].clientWidth / 2 + container[0].offsetLeft);
var offsets = {};
offsets[col1] = 0;
offsets[col2] = 0;
var newLine = true;
for (var i = 0; i < widgets.length; i++) {
var w = widgets[i];
var marginTop = 0;
if (offsets[col1] < offsets[col2]) {
marginTop = (offsets[col2] - offsets[col1]) * -1;
}
if(offsets[col1] <= offsets[col2] || w.columnSpan == 2){
newLine = true;
}
var margin = 'margin-top:' + marginTop + 'px;';
var height = 'height:' + w.height + 'px;';
var color = 'background-color:' + colorFactory() + ';';
var width = 'width:' + w.width + '%;';
var padding = newLine ? "padding-left:10px;" : "";
var component = $('<div class="widget" style="' + padding + margin + height + width + '"><div style="' + color + '">' + i + '</div></div>');
component.appendTo(container);
var c = component[0];
var index = 0;
var minOffset = null;
for(var p in offsets){
if(minOffset == null || offsets[p] < minOffset){
minOffset = offsets[p];
}
if(p == Math.floor(c.offsetLeft)){
index = 1;
}
if(index > 0 && index <= w.columnSpan){
offsets[p] = c.offsetTop + c.offsetHeight;
index++;
}
}
newLine = minOffset >= offsets[col1];
}
});

Resources