Skip to content

Instantly share code, notes, and snippets.

@knubie
Created April 28, 2015 16:02
Show Gist options
  • Save knubie/92efd8f69d7d8bb109ca to your computer and use it in GitHub Desktop.
Save knubie/92efd8f69d7d8bb109ca to your computer and use it in GitHub Desktop.
# My attempt at generalizing a common feature among C&T sites.
class Transporter
scroll = if 'ontouchmove' of window then 'ontouchmove' else 'scroll'
scroll = 'scroll'
history.pushState ?= -> null
$window = $(window)
$document = $(document)
LOADER_HEIGHT = null
constructor: ({@content, @footer, @paths, @onPageLoad, @onPageChange}) ->
@index = 0
@loading = no
@gettingPage = no
@pages = []
@pages.push new Page $(@content), $(@footer)
@$loader = $('<div class="transporter_loader"></div>')
$('body').append @$loader.css 'visibility', 'hidden'
LOADER_HEIGHT = @$loader.height()
@$loader.remove().css 'visibility', ''
$('.site_nav_jump--secondary_menu').attr('data-index', @index)
$window.on scroll, => @onScroll()
onScroll: ->
if @isDesktop()
# Insert or remove loader.
@insertLoader() if not @loading and @footerAboveBottom() and @paths[@index]?
@removeLoader() if @loading and @footerBelowBottom()
# When scrolling up, activate the pevious Page.
if @pages.length > 1 and @index > 0 and $window.scrollTop() <= 0
# Unfix the previous page so it scrolls with the window.
@previousPage().unfixWrapper()
@previousPage().$wrapper.show().css
'transform': "translateY(0)"
@currentPage().$content.css 'margin-top', '60px'
$window.scrollTop @previousPage().absoluteHeight() + LOADER_HEIGHT - $window.height()
@index--
history.pushState {}, '', if @index is 0 then '/' else @paths[@index - 1]
@onPageChange?()
# When scrolling down, activate the next Page.
if @pages.length > (@index + 1) and
@scrollBottom() > @currentPage().absoluteHeight() + LOADER_HEIGHT
# Save the current scrollBottom
scrollBottom = @scrollBottom()
# Fix current page in place so new page scrolls over it.
@currentPage().fixWrapper()
# Push the new Page into place and add an opaque background.
@nextPage().$content.css
'background-color': 'white'
'margin-top': $window.height() - LOADER_HEIGHT
# Reset the scroll position by subtracting
# the height of the current Page.
$window.scrollTop \
Math.max(scrollBottom - (@currentPage().absoluteHeight() + LOADER_HEIGHT), 1)
history.pushState({}, "", @paths[@index])
@index++
# Hide or show the previous page when it's covered or uncovered
# by the current page.
if @pages.length > 1 and @index > 0
if $window.scrollTop() >= ($window.height() - LOADER_HEIGHT)
@previousPage().$wrapper.hide().css('visibility', 'hidden')
@onPageChange?()
else
@previousPage().$wrapper.show().css
'visibility': 'visible'
# Parallax effect.
'transform': "translateY(-#{$window.scrollTop()/2}px)"
# Insert new page.
if @footerInView() and not @gettingPage and @paths[@index]?
@gettingPage = yes
@insertLoader() if @isMobile()
@getPage @index, (html) =>
$content = $(html).find(@content)
if @loading
if @isDesktop()
# If the user is scrolled up a little so that the loader is
# partially hidden, we need to compensate for that by
# adding negative margin to the bottom of the loader.
_distanceFromBottom = @distanceFromBottom()
@$loader.css('margin-bottom', Math.min((0 - _distanceFromBottom), 0))
$content.css 'margin-top', '60px'
@loading = false
@currentPage().unfixFooter()
@$loader.last().animate
'height': _distanceFromBottom
, 500, => @removeLoader()
else # isMobile
@loading = false
@$loader.last().animate
'height': 0
, 500, => @removeLoader()
# Create new Page object.
@pages.push new Page $content, @currentPage().$footer.clone(true)
# Append page to the site.
@currentPage().$wrapper.after @nextPage().$wrapper
# Perform init operations on new Page.
@onPageLoad? @nextPage().$content, html
@gettingPage = no
@index++ if @isMobile()
insertLoader: ->
if @isDesktop()
@currentPage().$footer.before @$loader
# Pad the bottom of the loader so it sits above the footer.
@$loader.css('margin-bottom', "#{@pages[@index].$footer.outerHeight()}px")
@currentPage().fixFooter()
else
@currentPage().$footer.after @$loader
@loading = yes
removeLoader: ->
@currentPage().unfixFooter()
@$loader.remove()
@$loader.css('height', '90px')
@loading = no
getPage: (index, callback) ->
$.ajax
type: 'POST'
url: @paths[index]
.done (html) ->
setTimeout ->
callback? html
, 1000
.fail -> console.log 'page injection failed'
# Helpers
scrollBottom: -> # return Number
$window.scrollTop() + $window.height()
previousPage: -> # return {Page}
@pages[@index - 1]
currentPage: -> # return {Page}
@pages[@index]
nextPage: -> # return {Page}
@pages[@index + 1]
footerInView: -> # return Boolean
footerTop = $document.height() - $(@footer).last().outerHeight()
@scrollBottom() >= footerTop
footerAboveBottom: -> # return Boolean
@scrollBottom() >= $document.height()
footerBelowBottom: -> # return Boolean
@scrollBottom() < $document.height() - LOADER_HEIGHT
distanceFromBottom: -> # return Number
$document.height() - @scrollBottom()
isMobile: -> # return Boolean
'ontouchmove' of window
#$window.width() < 768
isDesktop: -> # return Boolean
$window.width() >= 768
inView: (page) -> # return Boolean
@scrollBottom() > page.wrapperTop > $(window).scrollTop() - page.$wrapper.height()
class Page
LOADER_HEIGHT = 90
$window = $(window)
constructor: (@$content, @$footer) ->
@$wrapper = @$content.add(@$footer).wrapAll('<div class="transporter-wrapper" />').parent()
@wrapperTop = @$wrapper.position().top
fixFooter: ->
@$footer.css
'position': 'fixed'
'bottom': '0'
'width': "#{$window.width() - @$footer.position().left}px"
#'width': "calc(100vw - #{@$footer.position().left}px)"
'z-index': '24' # One less than the sticky nav on mobile.
unfixFooter: ->
@$footer.css
'position': 'relative'
'bottom': '0'
'width': ''
'z-index': '100'
fixWrapper: ->
@$wrapper.css
'position': 'fixed'
'bottom': LOADER_HEIGHT
'z-index': -1
'width': "#{$window.width() - @$wrapper.position().left}px"
#'width': "calc(100vw - #{@$wrapper.position().left}px)"
unfixWrapper: ->
@$wrapper.css
'position': 'relative'
'bottom': ''
'z-index': ''
'width': ''
absoluteHeight: ->
@$content.outerHeight(true) + @$footer.outerHeight(true) + @wrapperTop
new Transporter
content: '.content'
footer: '.site_footer_container'
paths: paths
onPageLoad: ($content, html) ->
# Do stuff on new page load.
onPageChange: ->
# Do some stuff on page change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment