Inspired by flickr/justified-layout
Last active
May 4, 2017 09:18
-
-
Save 599316527/f726400400dbffc699570ef15fb667de to your computer and use it in GitHub Desktop.
Waterfall Layout Calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {merge} from 'lodash' | |
const defaultContainerPadding = { | |
top: 0, | |
bottom: 0, | |
left: 0, | |
right: 0 | |
} | |
const defaultBoxMargin = { | |
top: 0, | |
bottom: 0, | |
left: 0, | |
right: 0 | |
} | |
const defaultOptions = { | |
containerWidth: 960, | |
containerPadding: defaultContainerPadding, | |
columnCount: 3, | |
columnGap: 10, | |
columnStepHeightThreshold: 0, | |
boxMargin: defaultBoxMargin | |
} | |
class Waterfall { | |
constructor(items, options) { | |
this.items = items.map(function (item) { | |
if (typeof item === 'number') { | |
return item | |
} | |
else { | |
let {width, height} = item | |
return width / height | |
} | |
}) | |
if (this.items.some(item => isNaN(item))) { | |
throw new Error('Illegal inputs') | |
} | |
merge(this, defaultOptions, options) | |
} | |
getBoxes() { | |
let paddingBoxWidth = this.containerWidth - this.containerPadding.left - this.containerPadding.right | |
let columnWidth = (paddingBoxWidth - (this.columnCount - 1) * this.columnGap) / this.columnCount | |
let columnPostions = Array.from(new Array(this.columnCount)).map((item, index) => { | |
return { | |
top: this.containerPadding.top, | |
left: this.containerPadding.left + (this.columnGap + columnWidth) * index | |
} | |
}) | |
let itemWidth = columnWidth - this.boxMargin.left - this.boxMargin.right | |
let currentColumnIndex = 0 | |
let boxes = this.items.map((aspectRatio, index) => { | |
let pos = columnPostions[currentColumnIndex] | |
if (index > 0) { | |
for (let i = 0; i < this.columnCount; i++) { | |
// find next proper column to place the item | |
let nextColumnIndex = (currentColumnIndex + i + 1) % this.columnCount | |
let nextPos = columnPostions[nextColumnIndex] | |
if (nextPos.top - pos.top < this.columnStepHeightThreshold) { | |
pos = nextPos | |
currentColumnIndex = nextColumnIndex | |
break | |
} | |
} | |
} | |
let top = pos.top + this.boxMargin.top | |
let left = pos.left + this.boxMargin.left | |
let height = itemWidth / aspectRatio | |
pos.top = top + height + this.boxMargin.bottom | |
return {top, left, height, width: itemWidth} | |
}) | |
this.boxes = boxes | |
return boxes | |
} | |
getContainerHeight() { | |
if (!this.boxes) { | |
this.getBoxes() | |
} | |
let boxes = this.boxes | |
let lastItemPerColumn = boxes.slice(Math.max(0, boxes.length - this.columnCount), boxes.length) | |
return Math.max.apply(Math, lastItemPerColumn.map(item => item.top + item.height)) | |
+ this.boxMargin.bottom + this.containerPadding.bottom | |
} | |
} | |
export default function (items, options) { | |
let wf = new Waterfall(items, options) | |
return { | |
boxes: wf.getBoxes(), | |
containerHeight: wf.getContainerHeight() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment