Skip to content

Instantly share code, notes, and snippets.

@efmeeks
Created August 28, 2017 17:55
Show Gist options
  • Save efmeeks/60a03369971602c7ee9d231b385c6ddd to your computer and use it in GitHub Desktop.
Save efmeeks/60a03369971602c7ee9d231b385c6ddd to your computer and use it in GitHub Desktop.
Scrolling Golden Spiral
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="js-spiral">
<div class="js-section">
<div class="message">
Scroll, Use Arrow Keys or Click<br><br>
<h2>I used this scrolling interface to create <a href="https://narrowdesign.com" target="_blank">narrowdesign.com</a></h2>
</div>
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
<div class="js-section">
</div>
</div>
</body>
</html>
$(function() {
var WIN = $(window);
var sections = $('.js-section');
var spiral = $('.js-spiral')
var _winW;
var _winH;
var smallScreen;
var landscape;
var aspect = .618033;
var axis = .7237;
var spiralOrigin;
var rotation = 0;
var sectionCount = sections.length;
var currentSection = 0;
var touchStartY = 0;
var touchStartX = 0;
var moved = 0;
var animRAF;
var animating = false;
var scrollTimeout;
var userAgent = window.navigator.userAgent.toLowerCase(),
firefox = userAgent.indexOf('firefox') != -1 || userAgent.indexOf('mozilla') == -1,
ios = /iphone|ipod|ipad/.test( userAgent ),
safari = (userAgent.indexOf('safari') != -1 && userAgent.indexOf('chrome') == -1) || ios,
linux = userAgent.indexOf('linux') != -1,
windows = userAgent.indexOf('windows') != -1;
resizeHandler();
// EVENTS
/////////
WIN.on('resize',resizeHandler);
WIN.on('scroll',function(e){
e.preventDefault();
})
WIN.on('wheel', function(e) {
var deltaY = -e.originalEvent.deltaY;
if (windows || linux) {
deltaY = e.deltaY * 5;
}
moved = -deltaY || 0;
rotation += moved/-10;
rotation = trimRotation();
e.preventDefault();
startScrollTimeout()
cancelAnimationFrame(animRAF);
scrollHandler();
});
WIN.on('touchstart', function(e) {
e.preventDefault()
var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
moved = 0;
touchStartX = touch.pageX;
touchStartY = touch.pageY;
cancelAnimationFrame(animRAF);
})
WIN.on('touchmove', function(e) {
e.preventDefault()
var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
moved = ((touchStartY - touch.pageY)+(touchStartX - touch.pageX)) * 3;
touchStartX = touch.pageX;
touchStartY = touch.pageY;
rotation += moved/-10;
rotation = trimRotation();
startScrollTimeout();
cancelAnimationFrame(animRAF);
scrollHandler()
});
WIN.on('touchend', function(e) {
animateScroll()
})
WIN.on('keydown',function(e) {
if (e.keyCode === 39 || e.keyCode === 40 || e.keyCode === 32) {
cancelAnimationFrame(animRAF);
animateScroll((currentSection + 1) * -90,rotation)
} else if (e.keyCode === 37 || e.keyCode === 38) {
cancelAnimationFrame(animRAF);
animateScroll((currentSection - 1) * -90,rotation)
}
scrollHandler()
})
sections.on('click',function() {
cancelAnimationFrame(animRAF)
animateScroll($(this).index() * -90,rotation);
})
// FUNCTIONS
////////////
function scrollHandler() {
requestAnimationFrame(function(){
var scale = Math.pow(aspect,rotation/90);
currentSection = Math.min(sectionCount + 2,Math.max(-sectionCount,Math.floor((rotation-30)/-90)));
spiral.css({
transform: 'rotate(' + rotation + 'deg) scale(' + scale + ')',
})
sections.removeClass('active')
sections.eq(currentSection).addClass('active')
})
}
function animateScroll(targR,startR,speed) {
var distance = startR - targR;
var mySpeed = speed || .2;
if (((targR || Math.abs(targR) === 0) && Math.abs(targR - rotation) > .1) || Math.abs(moved) > 1) {
if (targR || Math.abs(targR) === 0) {
rotation += mySpeed * (targR - rotation);
} else {
moved *= .98;
rotation += moved/-10;
}
rotation = trimRotation();
scrollHandler();
animRAF = requestAnimationFrame(function(){
animateScroll(targR,startR,speed)
});
} else if (targR || Math.abs(targR) === 0) {
cancelAnimationFrame(animRAF)
rotation = targR;
rotation = trimRotation();
scrollHandler();
}
}
function buildSpiral() {
// rotate around this point
spiralOrigin = Math.floor(_winW * axis) + 'px ' + Math.floor(_winW * aspect * axis) +'px';
var w = _winW * aspect;
var h = w; // they're squares
if (smallScreen && !landscape) { // flip it 90deg if it's a portrait phone
spiralOrigin = Math.floor((_winW/aspect) * aspect * (1 - axis)) +'px ' + Math.floor((_winW/aspect) * axis) + 'px ';
w = _winW;
h = _winW;
}
// HACK to smooth out Chrome vs Safari/Firefox
var translate = '';
if (safari || firefox) {
translate = 'translate3d(0,0,0)'
}
// END HACK
spiral.css({
transformOrigin: spiralOrigin,
backfaceVisiblity: 'hidden'
})
sections.each(function(i){
var myRot = Math.floor(90*i);
var scale = Math.pow(aspect, i);
$(this).css({
width: w,
height: h,
transformOrigin: spiralOrigin,
backfaceVisiblity: 'hidden',
backgroundColor: 'rgb(' + Math.floor(255-i*(255/sectionCount)) + ',50,50)',
transform: 'rotate(' + myRot + 'deg) scale(' + Math.pow(aspect, i) + ') ' + translate
})
})
scrollHandler();
}
function resizeHandler () { // Set the size of images and preload them
_winW = window.innerWidth/(1000/window.innerHeight);
_winH = window.innerHeight;
smallScreen = _winW < 960;
landscape = _winH < _winW;
buildSpiral()
}
// keep it from getting too small or too big
function trimRotation() {
return Math.max(-1500, Math.min(1200, rotation))
}
// if no scrolling happens for 200ms, animate to the closest section
function startScrollTimeout () {
clearTimeout(scrollTimeout)
if (currentSection > -1 && currentSection < sectionCount) {
scrollTimeout = setTimeout(function(){
cancelAnimationFrame(animRAF);
animateScroll(currentSection * -90,rotation,.15);
},200);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
body {
padding: 0;
margin: 0;
background-color: #113232;
color: #fff;
}
a {
color: #fff;
}
.js-spiral {
position: fixed;
}
.js-section {
position: absolute;
left: -100%;
box-sizing: border-box;
transition: .2s border ease-out;
border: 2px solid #113232;
}
.js-section:hover {
border-radius: 5%;
border: 10px solid #113232;
}
.message {
position: absolute;
width: 50%;
left: 5vh;
bottom: 10vh;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment