Last active
October 5, 2015 15:57
-
-
Save bkad/2831019 to your computer and use it in GitHub Desktop.
photo collage layout
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
class window.CollageLayout | |
constructor: (@picsPerLoad=20) -> | |
init: => | |
@oldestID = null | |
@cache = columns: [], loaded: false | |
@photoIDs = {} | |
$(window).on("resize", @handleResize) | |
@handleResize(true) | |
$(window).scroll(@fetchThumbnails) | |
@fetchThumbnails() | |
@semaphore = false | |
setThumbnailsContainerHeight: -> | |
maxHeight = _.max(@cache.columns) | |
$("#thumbnails").css(height: maxHeight + Math.floor(@collageWidth / 5)) | |
maxHeight | |
fetchThumbnails: => | |
out = [] | |
return false if @semaphore | |
return if @cache.loaded | |
isLastImageInRange = -> | |
$("#thumbnails .thumbnail:last img").offset().top >= $(window).scrollTop() + $(window).height() * 2 | |
return unless $("#thumbnails .thumbnail").length is 0 or !isLastImageInRange() | |
return false if @semaphore | |
@semaphore = true | |
$.ajax( | |
url: "/collage/api/spotlight-photos" | |
dataType: "json" | |
data: | |
id: @oldestID | |
limit: @picsPerLoad | |
crossDomain: false | |
type: "GET" | |
success: (data) => | |
callback = (html) -> out.push(html) | |
dfds = (@generateThumbnail(photo, callback) for photo in data) | |
$.when(dfds...) | |
.then => | |
$("#thumbnails").append(out.join("")) | |
$("#thumbnails img.notloaded").on("load", | |
(event) -> $(event.currentTarget).removeClass("notloaded")) | |
@handleResize() | |
@setThumbnailsContainerHeight() | |
@semaphore = false | |
@oldestID = data[data.length - 1].id if data.length > 0 | |
if data.length < @picsPerLoad or out.length < @picsPerLoad / 4 | |
@cache.loaded = true | |
else | |
setTimeout(@fetchThumbnails, 200) | |
) | |
getSpacings: (windowWidth) => | |
spacings = | |
photoSpacing: 3 | |
contentPadding: 30 | |
panelBorder: 18 | |
if !windowWidth? | |
windowWidth = @cache.lastWindowWidth | |
else | |
@cache.lastWindowWidth = windowWidth | |
if windowWidth > 1280 | |
spacings.collageSize = 250 | |
else if windowWidth > 800 | |
spacings.collageSize = 230 | |
else if windowWidth > 640 | |
spacings.collageSize = 178 | |
else if windowWidth > 320 | |
spacings.collageSize = 110 | |
else | |
spacings.collageSize = 96 | |
if windowWidth <= 800 | |
spacings.contentPadding = 12 | |
spacings.panelBorder = 12 | |
else if windowWidth <= 640 | |
spacings.contentPadding = 6 | |
spacings.panelBorder = 6 | |
spacings | |
generateThumbnail: (photo, callback) => | |
dfd = new $.Deferred() | |
return "" unless !@photoIDs[photo.id]? and photo.url | |
img = new Image() | |
@photoIDs[photo.id] = 1 | |
img.onload = (event) => | |
photo.width = event.target.width | |
photo.height = event.target.height | |
photoStyle = @imageCollageStyle(photo) | |
callback( | |
""" | |
<a href="" class="thumbnail" data-photo-id="#{photo.id}" data-width="#{photo.width}" | |
data-height="#{photo.height}" style="#{photoStyle.containerStyle}"> | |
<img src="#{photo.url}" style="#{photoStyle.imgStyle}" class="notloaded"/> | |
</a> | |
""") | |
dfd.resolve() | |
img.src = photo.url | |
dfd.promise() | |
imageCollageStyle: (photo) => | |
spacings = @getSpacings() | |
column = 0 | |
columnsOccupied = 1 | |
columnStartAdjust = 0 | |
columnCache = @cache.columns | |
minHeight = _.min(columnCache) | |
column = _.indexOf(columnCache, minHeight) | |
sizes = @fitSize([@collageWidth, 0], [photo.width, photo.height]) | |
isWide = if photo.width > photo.height then 0.4 else 0.9 | |
if Math.random() > isWide and photo.width > 360 | |
if columnCache[column] is columnCache[column + 1] | |
newcol = Math.max(columnCache[column + 1], columnCache[column]) | |
columnStartAdjust = 0 | |
sizes = @fitSize([@collageWidth * 2, 0], [photo.width, photo.height]) | |
columnsOccupied = 2 | |
else if columnCache[column] is columnCache[column - 1] | |
newcol = Math.max(columnCache[column - 1], columnCache[column]) | |
columnStartAdjust = -1 | |
sizes = @fitSize([@collageWidth * 2, 0], [photo.width, photo.height]) | |
columnsOccupied = 2 | |
startAdjustColumn = columnCache[column + columnStartAdjust] | |
startAdjustColumnPrevious = columnCache[column + columnStartAdjust - 1] | |
startAdjustColumnEnd = columnCache[column + columnStartAdjust + columnsOccupied] | |
if Math.abs(startAdjustColumn + sizes[1] - startAdjustColumnPrevious) < @collageWidth / 5 | |
sizes[1] = startAdjustColumnPrevious - startAdjustColumn | |
else if Math.abs(startAdjustColumn + sizes[1] - startAdjustColumnEnd) < @collageWidth / 5 | |
sizes[1] = startAdjustColumnEnd - startAdjustColumn | |
width = sizes[0] - spacings.photoSpacing | |
height = sizes[1] - spacings.photoSpacing | |
containerStyle = "width:#{width}px; height:#{height}px; " + | |
"margin-left:#{(column + columnStartAdjust) * @collageWidth}px; " + | |
"margin-top:#{columnCache[column]}px;" | |
for i in [(column + columnStartAdjust)...(column + columnStartAdjust + columnsOccupied)] | |
columnCache[i] += sizes[1] | |
imgSize = @fillSize([width, height], [photo.width, photo.height]) | |
imgStyle = "width:#{imgSize[0]}px; height:#{imgSize[1]}px; " + | |
"margin-left:#{Math.round((width - imgSize[0]) / 2)}px; " + | |
"margin-top:#{Math.floor((height - imgSize[1]) / 2)}px;" | |
containerStyle: containerStyle | |
imgStyle: imgStyle | |
width: width | |
height: height | |
handleResize: (isFirst) => | |
windowWidth = $(window).width() | |
spacings = @getSpacings(windowWidth) | |
numberOfThumbnails = $("#thumbnails a").length | |
originalCollageWidth = @collageWidth | |
thumbnailSize = @collageWidth = spacings.collageSize | |
oldColumnCount = -1 if @collageWidth isnt originalCollageWidth | |
extraPadding = spacings.panelBorder * 2 - spacings.photoSpacing | |
columnCount = Math.floor((windowWidth - spacings.contentPadding * 2 - extraPadding) / thumbnailSize) | |
columnCount = Math.min(15, columnCount) | |
newWidth = columnCount * thumbnailSize | |
oldColumnCount = @cache.columns.length | |
return if oldColumnCount is columnCount and @collageWidth is originalCollageWidth | |
@cache.columns = [] | |
@cache.columns.push(0) for i in [0...columnCount] | |
#TODO: same seed for randomness by day | |
$("#content").css( | |
margin: "auto" | |
width: newWidth + extraPadding | |
) | |
for thumbnail in $("#thumbnails .thumbnail") | |
$thumbnail = $(thumbnail) | |
photo = width: $thumbnail.data("width"), height: $thumbnail.data("height") | |
style = @imageCollageStyle(photo) | |
imgElement = $thumbnail.attr("style", style.containerStyle).find("img").attr("style", style.imgStyle) | |
if isFirst and style.width > 360 * (window.devicePixelRatio or 1) | |
img = $thumbnail.find("img").attr("href") | |
imgElement.attr("src", img) | |
@setThumbnailsContainerHeight() | |
fitSize: (boundSize, photoSize) => @fitOrFillSize(boundSize, photoSize, "fit") | |
fillSize: (boundSize, photoSize) => @fitOrFillSize(boundSize, photoSize, "fill") | |
fitOrFillSize: (boundSize, photoSize, mode) -> | |
isFit = mode is "fit" | |
boundSize[0] = parseInt(boundSize[0], 10) | |
boundSize[1] = parseInt(boundSize[1], 10) | |
photoSize[0] = parseInt(photoSize[0], 10) | |
photoSize[1] = parseInt(photoSize[1], 10) | |
boundSize[1] = Infinity if boundSize[1] is 0 | |
boundSize[0] = Infinity if boundSize[0] is 0 | |
if (boundSize[0] / boundSize[1] < photoSize[0] / photoSize[1]) is isFit | |
_.map([boundSize[0], photoSize[1] * boundSize[0] / photoSize[0]], Math.floor) | |
else | |
_.map([photoSize[0] * boundSize[1] / photoSize[1], boundSize[1]], Math.floor) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment