Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Lorezz/f126dd3e18198d68d30c899e5c4cab3c to your computer and use it in GitHub Desktop.
Save Lorezz/f126dd3e18198d68d30c899e5c4cab3c to your computer and use it in GitHub Desktop.
Horizontal scroll, drag, transition, bounce
<figure class="logo logo--top js-trigger">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/logo.svg">
</figure>
<a href="https://codepen.io/ReGGae/full/QZxdVX/" target="_blank" class="resize">
<div class="resize__inner">
<figure class="logo logo--resize">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/logo.svg">
</figure>
<p>Please view in <span>full page</span> mode</p>
</div>
</a>
<a href="https://twitter.com/Jesper_Landberg?lang=en" target="_blank" class="hi">
Hi
</a>
<a href="https://dribbble.com/shots/5321013-Habital-Showcase-Alternative" target="_blank" rel="nofollow" class="menu-btn js-menu-btn">
<div class="menu-btn__circles">
<span class="menu-btn__circle menu-btn__circle--top js-menu-btn__circle--top"></span>
<span class="menu-btn__circle menu-btn__circle--bottom js-menu-btn__circle--bottom"></span>
</div>
<div class="menu-btn__text">See shot</div>
</a>
<div class="scroll" data-scroll>
<nav class="filter">
<ul class="filter__list">
<li class="filter__item">
<a href="#" class="filter__link is-active js-trigger">
<div class="filter__link-mask" area-hidden><span>Interiors</span></div>
Interiors
</a>
</li>
<li class="filter__item">
<a href="#" class="filter__link js-trigger">
<div class="filter__link-mask" area-hidden><span>Residential</span></div>
Residential
</a>
</li>
<li class="filter__item">
<a href="#" class="filter__link js-trigger">
<div class="filter__link-mask" area-hidden><span>Commercial</span></div>
Commercial
</a>
</li>
<li class="filter__item">
<a href="#" class="filter__link js-trigger">
<div class="filter__link-mask" area-hidden><span>Installation</span></div>
Installation
</a>
</li>
</ul>
</nav>
<div class="scroll-content" data-scroll-content>
<article class="slide slide--1 js-slide">
<div class="slide__inner">
<div class="slide__img js-transition-img">
<figure class="js-transition-img__inner">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/project-one.png" draggable="false">
</figure>
</div>
</div>
</article>
<article class="slide slide--2 js-slide">
<div class="slide__inner">
<div class="slide__img js-transition-img">
<figure class="js-transition-img__inner">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/project-two.png" draggable="false">
</figure>
</div>
</div>
</article>
<article class="slide slide--3 js-slide">
<div class="slide__inner">
<div class="slide__img">
<figure>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/project-three.png" draggable="false">
</figure>
</div>
</div>
</article>
<article class="slide slide--1 js-slide">
<div class="slide__inner">
<div class="slide__img">
<figure>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/project-one.png" draggable="false">
</figure>
</div>
</div>
</article>
<article class="slide slide--2 js-slide">
<div class="slide__inner">
<div class="slide__img">
<figure>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/project-two.png" draggable="false">
</figure>
</div>
</div>
</article>
<article class="slide slide--3 js-slide">
<div class="slide__inner">
<div class="slide__img">
<figure>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/project-three.png" draggable="false">
</figure>
</div>
</div>
</article>
</div>
<div class="scroll-content scroll-content--last" data-scroll-content>
<article class="slide slide--1 js-slide">
<div class="slide__inner">
<div class="slide__sub-title"><span>Project</span></div>
<h1 class="slide__title"><div class="js-transition-title">Oak Refuge</div></h1>
<div class="slide__img slide__img--proxy"></div>
<div class="slide__project">Corpus Studio</div>
</div>
</article>
<article class="slide slide--2 js-slide">
<div class="slide__inner">
<div class="slide__sub-title"><span>Project</span></div>
<h1 class="slide__title"><div class="js-transition-title">Teton Residence</div></h1>
<div class="slide__img slide__img--proxy"></div>
<div class="slide__project">Ro Rocket Design</div>
</div>
</article>
<article class="slide slide--3 js-slide">
<div class="slide__inner">
<div class="slide__sub-title"><span>Project</span></div>
<h1 class="slide__title">Oak Refuge</h1>
<div class="slide__img slide__img--proxy"></div>
<div class="slide__project">Corpus Studio</div>
</div>
</article>
<article class="slide slide--1 js-slide">
<div class="slide__inner">
<div class="slide__sub-title"><span>Project</span></div>
<h1 class="slide__title">Teton Residence</h1>
<div class="slide__img slide__img--proxy"></div>
<div class="slide__project">Ro Rocket Design</div>
</div>
</article>
<article class="slide slide--2 js-slide">
<div class="slide__inner">
<div class="slide__sub-title"><span>Project</span></div>
<h1 class="slide__title">Oak Refuge</h1>
<div class="slide__img slide__img--proxy"></div>
<div class="slide__project">Corpus Studio</div>
</div>
</article>
<article class="slide slide--3 js-slide">
<div class="slide__inner">
<div class="slide__sub-title"><span>Project</span></div>
<h1 class="slide__title">Teton Residence</h1>
<div class="slide__img slide__img--proxy"></div>
<div class="slide__project">Ro Rocket Design</div>
</div>
</article>
</div>
<div class="scrollbar" data-scrollbar>
<div class="scrollbar__handle js-scrollbar__handle"></div>
</div>
</div>
<div class="mask js-mask">
<div class="mask__slice js-mask__slice"></div>
<div class="mask__slice js-mask__slice"></div>
<div class="mask__slice js-mask__slice"></div>
<div class="mask__inner">
<figure class="logo logo--mask">
<img class="js-logo" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/logo.svg">
</figure>
<div class="mask-line js-mask-line">
<div class="mask-line__inner js-mask-line"></div>
</div>
</div>
</div>
const math = {
lerp: (a, b, n) => {
return (1 - n) * a + n * b
},
norm: (value, min, max) => {
return (value - min) / (max - min)
}
}
const config = {
height: window.innerHeight,
width: window.innerWidth
}
class Smooth {
constructor() {
this.bindAll()
this.el = document.querySelector('[data-scroll]')
this.content = [...document.querySelectorAll('[data-scroll-content]')]
this.dom = {
el: this.el,
content: this.content,
elems: [[...this.content[0].querySelectorAll('.js-slide')], [...this.content[1].querySelectorAll('.js-slide')]],
handle: this.el.querySelector('.js-scrollbar__handle')
}
this.data = {
total: this.dom.elems[0].length - 1,
current: 0,
last: {
one: 0,
two: 0
},
on: 0,
off: 0
}
this.bounds = {
elem: 0,
content: 0,
width: 0,
max: 0,
min: 0
}
this.state = {
dragging: false
}
this.rAF = null
this.parallax = null
this.init()
}
bindAll() {
['scroll', 'run', 'resize']
.forEach((fn) => this[fn] = this[fn].bind(this))
}
setStyles() {
this.dom.el.style.position = 'fixed';
this.dom.el.style.top = 0;
this.dom.el.style.left = 0;
this.dom.el.style.height = '100%'
this.dom.el.style.width = '100%'
this.dom.el.style.overflow = 'hidden'
}
setBounds(elems) {
let w = 0
elems.forEach((el, index) => {
const bounds = el.getBoundingClientRect()
el.style.position = 'absolute'
el.style.top = 0
el.style.left = `${w}px`
w = w + bounds.width
this.bounds.width = w
this.bounds.max = this.bounds.width - config.width
console.log(this.bounds.width, this.bounds.max)
if ((this.data.total) === index && elems === this.dom.elems[0]) {
this.dom.content[0].style.width = `${w}px`
this.dom.content[1].style.width = `${w}px`
document.body.style.height = `${w}px`
}
})
}
resize() {
this.setBounds(this.dom.elems[0])
this.setBounds(this.dom.elems[1])
this.scroll()
}
preload() {
imagesLoaded(this.dom.content, (instance) => {
this.setBounds(this.dom.elems[0])
this.setBounds(this.dom.elems[1])
})
}
scroll() {
if (this.state.dragging) return
this.data.current = window.scrollY
this.clamp()
}
drag(e) {
this.data.current = window.scrollY - (e.clientX - this.data.on)
this.clamp()
}
clamp() {
this.data.current = Math.min(Math.max(this.data.current, 0), this.bounds.max)
}
run() {
this.data.last.one = math.lerp(this.data.last.one, this.data.current, 0.085)
this.data.last.one = Math.floor(this.data.last.one * 100) / 100
this.data.last.two = math.lerp(this.data.last.two, this.data.current, 0.08)
this.data.last.two = Math.floor(this.data.last.two * 100) / 100
const diff = this.data.current - this.data.last.one
const acc = diff / config.width
const velo =+ acc
const bounce = 1 - Math.abs(velo * 0.25)
const skew = velo * 7.5
this.dom.content[0].style.transform = `translate3d(-${this.data.last.one.toFixed(2)}px, 0, 0) scaleY(${bounce}) skewX(${skew}deg)`
this.dom.content[1].style.transform = `translate3d(-${this.data.last.two.toFixed(2)}px, 0, 0) scaleY(${bounce})`
const scale = math.norm(this.data.last.two, 0, this.bounds.max)
this.dom.handle.style.transform = `scaleX(${scale})`
this.requestAnimationFrame()
}
on() {
this.setStyles()
this.setBounds(this.dom.elems[0])
this.setBounds(this.dom.elems[1])
this.addEvents()
this.requestAnimationFrame()
}
requestAnimationFrame() {
this.rAF = requestAnimationFrame(this.run)
}
resize() {
this.setBounds()
}
addEvents() {
window.addEventListener('scroll', this.scroll, { passive: true })
this.dom.el.addEventListener('mousemove', (e) => {
if (!this.state.dragging) return
this.drag(e)
}, { passive: true })
this.dom.el.addEventListener('mousedown', (e) => {
this.state.dragging = true
this.data.on = e.clientX
})
window.addEventListener('mouseup', () => {
this.state.dragging = false
window.scrollTo(0, this.data.current)
})
}
init() {
this.preload()
this.on()
}
}
class Transition {
constructor() {
this.dom = {
mask: document.querySelector('.js-mask'),
slices: [...document.querySelectorAll('.js-mask__slice')],
triggers: [...document.querySelectorAll('.js-trigger')],
lines: [...document.querySelectorAll('.js-mask-line')],
logo: document.querySelector('.js-logo'),
images: [...document.querySelectorAll('.js-transition-img')],
imagesInner: [...document.querySelectorAll('.js-transition-img__inner')],
titles: [...document.querySelectorAll('.js-transition-title')]
}
this.tl = null
this.state = false
this.init()
}
resetScroll() {
window.scrollTo(0, 0)
}
createTimeline() {
this.tl = new TimelineMax({
paused: true,
onComplete: () => {
this.state = false
}
})
this.tl
.set([this.dom.images, this.dom.imagesInner], {
xPercent: 0,
scale: 1
})
.set(this.dom.titles, {
yPercent: 0
})
.set(this.dom.mask, {
autoAlpha: 1
})
.staggerFromTo(this.dom.slices, 1.5, {
xPercent: 100
}, {
xPercent: 0,
ease: Expo.easeInOut
}, -0.075)
.addCallback(this.resetScroll.bind(this))
.addLabel('loaderStart')
.set(this.dom.images, {
xPercent: -100
})
.set(this.dom.imagesInner, {
xPercent: 100
})
.set(this.dom.titles, {
yPercent: -100
})
.set([this.dom.lines[0], this.dom.logo], {
autoAlpha: 1
})
.fromTo(this.dom.logo, 1, {
yPercent: -100,
rotation: 10
}, {
yPercent: 0,
rotation: 0,
ease: Expo.easeOut
})
.staggerFromTo(this.dom.lines, 1, {
scaleX: 0
}, {
scaleX: 1,
ease: Expo.easeInOut
}, 0.75, "-=1")
.set(this.dom.lines, {
transformOrigin: 'right'
})
.fromTo(this.dom.lines[0], 1, {
scaleX: 1
}, {
scaleX: 0,
ease: Expo.easeInOut
})
.fromTo(this.dom.logo, 1, {
yPercent: 0
}, {
yPercent: 105,
ease: Expo.easeOut
}, "-=1")
.staggerFromTo(this.dom.slices, 1.5, {
xPercent: 0
}, {
xPercent: 100,
ease: Expo.easeInOut
}, 0.075)
.set(this.dom.mask, {
autoAlpha: 0
})
.addLabel('imagesStart', "-=0.85")
.staggerFromTo(this.dom.titles, 1.5, {
yPercent: 100
}, {
yPercent: 0,
ease: Expo.easeInOut
}, 0.05, 'imagesStart')
.staggerFromTo(this.dom.images, 1.25, {
xPercent: -100
}, {
xPercent: 0,
ease: Expo.easeInOut
}, 0.05, 'imagesStart')
.staggerFromTo(this.dom.imagesInner, 1.25, {
xPercent: 100
}, {
xPercent: 0,
ease: Expo.easeInOut
}, 0.05, 'imagesStart')
.addLabel('loaderEnd')
}
addEvents() {
this.dom.triggers.forEach(trigger => {
trigger.addEventListener('click', (e) => {
e.preventDefault()
if (this.state) return
this.dom.triggers.forEach(el => { el.classList.remove('is-active') })
trigger.classList.add('is-active')
this.state = true
this.tl.restart()
})
})
}
loader() {
this.resetScroll()
this.tl.tweenFromTo('loaderStart', 'loaderEnd')
}
init() {
this.createTimeline()
this.addEvents()
this.loader()
}
}
// Init classes
const smooth = new Smooth()
const transition = new Transition()
// See shot - hover
const btn = document.querySelector('.js-menu-btn')
btn.addEventListener('mouseenter', () => {
TweenMax.to('.js-menu-btn__circle--bottom', 0.5, {
y: 15,
alpha: 0,
ease: Expo.easeOut
})
TweenMax.set('.js-menu-btn__circle--top', {
autoAlpha: 1
})
TweenMax.fromTo('.js-menu-btn__circle--top', 0.75, {
y: -60
}, {
y: 0,
ease: Bounce.easeOut
})
})
btn.addEventListener('mouseleave', () => {
TweenMax.to('.js-menu-btn__circle--top', 0.5, {
y: 15,
alpha: 0,
ease: Expo.easeOut
})
TweenMax.set('.js-menu-btn__circle--bottom', {
autoAlpha: 1
})
TweenMax.fromTo('.js-menu-btn__circle--bottom', 0.75, {
y: -60
}, {
y: 0,
ease: Bounce.easeOut
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>
<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
@font-face {
font-family: 'font';
src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/OakesGrotesk-Semi-Bold.woff.woff2') format('woff2');
src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/OakesGrotesk-Semi-Bold.woff.woff2') format('woff');
font-weight: normal;
font-style: normal;
}
html, body{
height: 100%;
font-family: 'helvetica neue';
}
body{
height: 100%;
overflow-y: scroll;
padding: 0;
margin: 0;
background-color: #111;
user-select: none;
}
h1, h2{
font-weight: normal;
}
* {
box-sizing: border-box;
}
figure{
padding: 0;
margin: 0;
}
.scroll{
cursor: grab;
}
.scroll-content{
display: flex;
white-space: nowrap;
position: relative;
height: 100vh;
&--last{
position: absolute;
top: 0;
left: 0;
}
}
.hi{
position: fixed;
bottom: 2vw;
left: 2vw;
color: #fff;
font-size: 1vw;
z-index: 999;
text-decoration: none;
}
.logo{
position: relative;
&--top{
position: fixed;
top: 2vw;
left: 2vw;
z-index: 10;
img{
height: 1vw;
width: auto;
}
}
&--resize{
margin-bottom: 1rem;
img{
width: 10rem;
margin: 0 auto;
}
}
&--mask{
overflow: hidden;
margin-bottom: 2rem;
img{
width: 15rem;
height: auto;
margin: 0 auto;
visibility: hidden;
opacity: 0;
}
}
}
.menu-btn{
position: fixed;
top: 2vw;
right: 2vw;
display: flex;
align-items: center;
text-decoration: none;
z-index: 999;
&__circles{
position: relative;
height: 0.45vw;
width: 0.45vw;
margin-right: 0.75vw;
}
&__circle{
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
border-radius: 50%;
&--top{
visiblity: hidden;
opacity: 0;
}
}
&__text{
color: #fff;
font-size: 1vw;
}
}
.filter{
position: absolute;
top: 7.5%;
left: 50%;
transform: translateX(-50%);
z-index: 10;
&__list{
display: flex;
}
&__item{
display: block;
}
&__link{
position: relative;
display: block;
color: rgba(#fff, 0.5);
text-decoration: none;
padding: 0 1.5vw;
font-size: 1.15vw;
overflow: hidden;
&-mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #111;
transform: translate3d(-100%, 0, 0);
transition: transform .75s ease;
z-index: 2;
span{
display: block;
padding: 0 1.5vw;
font-size: 1.15vw;
transform: translate3d(100%, 0, 0);
transition: transform .75s ease;
color: #fff;
.filter__link:hover &,
.filter__link.is-active & {
transform: translate3d(0, 0, 0);
}
}
.filter__link:hover &,
.filter__link.is-active & {
transform: translate3d(0, 0, 0);
}
}
}
}
.slide{
display: flex;
width: 50vw;
height: 100%;
padding: 20vh 0;
&--1{
align-items: flex-start;
}
&--2{
align-items: flex-end;
}
&--3{
align-items: center;
}
&:last-child{
width: 65vw;
padding-right: 15vw;
}
&__inner{
position: relative;
padding-left: 15vw;
width: 100%;
}
&__sub-title{
position: absolute;
top: 15%;
left: 5vw;
color: rgba(#fff, 0.5);
font-size: 1vw;
}
&__title{
position: absolute;
top: 7.5%;
left: 7.5vw;
color: #fff;
font-size: 4vw;
z-index: 2;
overflow: hidden;
}
&__project{
color: #fff;
position: absolute;
top: 100%;
right: 5%;
font-size: 1.15vw;
padding-top: 1.5vw;
}
&__img{
position: relative;
overflow: hidden;
padding-top: 65%;
width: 100%;
figure{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
img{
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
object-fit: cover;
}
}
}
}
img{
display: block;
width: 100%;
height: auto;
}
.scrollbar{
position: absolute;
bottom: 7.5%;
left: 20%;
right: 20%;
height: 1px;
background-color: rgba(#fff, 0.25);
&__handle{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: scaleX(0);
transform-origin: left;
background-color: #fff;
}
}
.mask{
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
visibility: hidden;
opacity: 0;
&__slice{
flex: 1;
background-color: #000;
}
&__inner{
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
&-line{
position: relative;
transform-origin: left;
width: 20rem;
height: 2px;
overflow: hidden;
background-color: rgba(#fff, 0.25);
visibility: hidden;
opacity: 0;
&__inner{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
transform-origin: left;
}
}
}
.resize{
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
z-index: 9999;
&__inner{
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
color: #fff;
text-align: center;
text-decoration: none;
}
span{
color: rgba(#fff, 0.5);
}
@media (max-width: 800px) {
display: block;
}
@media (max-height: 600px) {
display: block;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment