Skip to content

Instantly share code, notes, and snippets.

@bkad
Last active October 5, 2015 15:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bkad/2831019 to your computer and use it in GitHub Desktop.
Save bkad/2831019 to your computer and use it in GitHub Desktop.
photo collage layout
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