Based on this amazing Dribbble shot by Nicholas - https://dribbble.com/shots/3774469-T-R-A-V-E-L-E-R
A Pen by Nikolay Talanov on CodePen.
Based on this amazing Dribbble shot by Nicholas - https://dribbble.com/shots/3774469-T-R-A-V-E-L-E-R
A Pen by Nikolay Talanov on CodePen.
<div id="app"></div> | |
<a href="https://dribbble.com/shots/3774469-T-R-A-V-E-L-E-R" target="_blank" class="icon-link"> | |
<img src="http://icons.iconarchive.com/icons/uiconstock/socialmedia/256/Dribbble-icon.png"> | |
</a> | |
<a href="https://twitter.com/NikolayTalanov" target="_blank" class="icon-link icon-link--twitter"> | |
<img src="https://cdn1.iconfinder.com/data/icons/logotypes/32/twitter-128.png"> | |
</a> |
class CitiesSlider extends React.Component { | |
constructor(props) { | |
super(props); | |
this.IMAGE_PARTS = 4; | |
this.changeTO = null; | |
this.AUTOCHANGE_TIME = 4000; | |
this.state = { activeSlide: -1, prevSlide: -1, sliderReady: false }; | |
} | |
componentWillUnmount() { | |
window.clearTimeout(this.changeTO); | |
} | |
componentDidMount() { | |
this.runAutochangeTO(); | |
setTimeout(() => { | |
this.setState({ activeSlide: 0, sliderReady: true }); | |
}, 0); | |
} | |
runAutochangeTO() { | |
this.changeTO = setTimeout(() => { | |
this.changeSlides(1); | |
this.runAutochangeTO(); | |
}, this.AUTOCHANGE_TIME); | |
} | |
changeSlides(change) { | |
window.clearTimeout(this.changeTO); | |
const { length } = this.props.slides; | |
const prevSlide = this.state.activeSlide; | |
let activeSlide = prevSlide + change; | |
if (activeSlide < 0) activeSlide = length - 1; | |
if (activeSlide >= length) activeSlide = 0; | |
this.setState({ activeSlide, prevSlide }); | |
} | |
render() { | |
const { activeSlide, prevSlide, sliderReady } = this.state; | |
return ( | |
<div className={classNames('slider', { 's--ready': sliderReady })}> | |
<p className="slider__top-heading">Travelers</p> | |
<div className="slider__slides"> | |
{this.props.slides.map((slide, index) => ( | |
<div | |
className={classNames('slider__slide', { 's--active': activeSlide === index, 's--prev': prevSlide === index })} | |
key={slide.city} | |
> | |
<div className="slider__slide-content"> | |
<h3 className="slider__slide-subheading">{slide.country || slide.city}</h3> | |
<h2 className="slider__slide-heading"> | |
{slide.city.split('').map(l => <span>{l}</span>)} | |
</h2> | |
<p className="slider__slide-readmore">read more</p> | |
</div> | |
<div className="slider__slide-parts"> | |
{[...Array(this.IMAGE_PARTS).fill()].map((x, i) => ( | |
<div className="slider__slide-part" key={i}> | |
<div className="slider__slide-part-inner" style={{ backgroundImage: `url(${slide.img})` }} /> | |
</div> | |
))} | |
</div> | |
</div> | |
))} | |
</div> | |
<div className="slider__control" onClick={() => this.changeSlides(-1)} /> | |
<div className="slider__control slider__control--right" onClick={() => this.changeSlides(1)} /> | |
</div> | |
); | |
} | |
} | |
const slides = [ | |
{ | |
city: 'Paris', | |
country: 'France', | |
img: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/142996/paris.jpg', | |
}, | |
{ | |
city: 'Singapore', | |
img: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/142996/singapore.jpg', | |
}, | |
{ | |
city: 'Prague', | |
country: 'Czech Republic', | |
img: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/142996/prague.jpg', | |
}, | |
{ | |
city: 'Amsterdam', | |
country: 'Netherlands', | |
img: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/142996/amsterdam.jpg', | |
}, | |
{ | |
city: 'Moscow', | |
country: 'Russia', | |
img: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/142996/moscow.jpg', | |
}, | |
]; | |
ReactDOM.render(<CitiesSlider slides={slides} />, document.querySelector('#app')); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.min.js"></script> |
*, *:before, *:after { | |
box-sizing: border-box; | |
margin: 0; | |
padding: 0; | |
} | |
body { | |
font-family: 'Roboto', Helvetica, Arial, sans-serif; | |
background: #000; | |
} | |
$numOfParts: 4; | |
$animTime: 1s; | |
$stagger: 0.08s; | |
$sliderReadyTrans: all $animTime/2 $animTime; | |
$maxLettersStagger: 6; | |
$letterStagger: 0.1s; | |
.slider { | |
overflow: hidden; | |
position: relative; | |
height: 100vh; | |
color: #fff; | |
@mixin sliderReady { | |
.slider.s--ready & { | |
@content; | |
} | |
} | |
&__top-heading { | |
z-index: $numOfParts*3; | |
position: absolute; | |
left: 0; | |
top: 100px; | |
width: 100%; | |
text-align: center; | |
font-size: 16px; | |
text-transform: uppercase; | |
letter-spacing: 2.5px; | |
transition: $sliderReadyTrans; | |
transform: translateY(-30px); | |
opacity: 0; | |
@include sliderReady { | |
transform: translateY(0); | |
opacity: 1; | |
} | |
} | |
&__slides { | |
position: relative; | |
height: 100%; | |
} | |
&__slide { | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
&.s--active { | |
pointer-events: auto; | |
} | |
@mixin slidePrev { | |
.slider__slide.s--prev & { | |
@content; | |
} | |
} | |
@mixin slideActive { | |
.slider__slide.s--active & { | |
@content; | |
} | |
} | |
&-content { | |
z-index: $numOfParts + 2;; | |
position: relative; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
flex-direction: column; | |
height: 100%; | |
text-transform: uppercase; | |
line-height: 1; | |
} | |
@mixin subTextsActiveSlide { | |
opacity: 0; | |
transition: $animTime/2; | |
@include slideActive { | |
transition-delay: $animTime*0.65; | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
&-subheading { | |
margin-bottom: 20px; | |
font-size: 24px; | |
letter-spacing: 2px; | |
transform: translateY(20px); | |
@include subTextsActiveSlide; | |
} | |
&-heading { | |
$fontSize: 60px; | |
display: flex; | |
margin-bottom: 20px; | |
font-size: $fontSize; | |
letter-spacing: 12px; | |
span { | |
display: block; | |
opacity: 0; | |
transform: translateY($fontSize*-1); | |
transition: all $animTime/3; | |
@include slidePrev { | |
transform: translateY($fontSize) | |
} | |
@include slideActive { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
@for $i from 1 through $maxLettersStagger { | |
&:nth-child(#{$i}) { | |
$delay: $letterStagger * ($i - 1); | |
transition-delay: $delay; | |
@include slideActive { | |
transition-delay: $delay + $animTime/3; | |
} | |
} | |
} | |
&:nth-child(n+#{$maxLettersStagger + 1}) { | |
$delay: $letterStagger * $maxLettersStagger; | |
transition-delay: $delay; | |
@include slideActive { | |
transition-delay: $delay + $animTime/3; | |
} | |
} | |
} | |
} | |
&-readmore { | |
position: relative; | |
font-size: 14px; | |
text-transform: lowercase; | |
backface-visibility: hidden; | |
transform: translateY(-20px); | |
cursor: pointer; | |
@include subTextsActiveSlide; | |
&:before { | |
content: ''; | |
position: absolute; | |
left: -2px; | |
top: -3px; | |
width: calc(100% + 4px); | |
height: calc(100% + 6px); | |
background: rgba(255,255,255,0.4); | |
transform: scaleX(0.3); | |
transform-origin: 0 50%; | |
transition: transform 0.3s; | |
} | |
&:hover:before { | |
transform: scaleX(1); | |
} | |
} | |
&-parts { | |
position: absolute; | |
left: 0; | |
top: 0; | |
display: flex; | |
width: 100%; | |
height: 100%; | |
&:after { | |
content: ''; | |
z-index: $numOfParts + 1; | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0,0,0,0.1); | |
} | |
} | |
&-part { | |
$partW: (100vw / $numOfParts); | |
position: relative; | |
width: percentage(1 / $numOfParts); | |
height: 100%; | |
$partRef: &; | |
$imageFadeAT: $animTime/4; | |
&-inner { | |
overflow: hidden; | |
position: relative; | |
width: 100%; | |
height: 100%; | |
background-size: 0 0; | |
background-repeat: no-repeat; | |
transition: transform $animTime/2 ease-in-out; | |
&:before { | |
content: ''; | |
position: absolute; | |
width: 100vw; | |
height: 100%; | |
background-image: inherit; | |
background-size: cover; | |
background-position: center center; | |
transition: opacity $imageFadeAT; | |
opacity: 0; | |
} | |
@for $i from 1 through $numOfParts { | |
#{$partRef}:nth-child(#{$i}) & { | |
$delayOut: ($numOfParts - $i) * $stagger; | |
$delayIn: $i * $stagger + $animTime/5; | |
z-index: $numOfParts - $i; | |
transition-delay: $delayOut; | |
transform: translateX(percentage($i / $numOfParts * -1.3)); | |
@include slideActive { | |
transition-delay: $delayIn; | |
} | |
&:before { | |
left: $partW * ($i - 1) * -1; | |
transition-delay: $delayOut + $imageFadeAT/2; | |
@include slideActive { | |
transition-delay: $delayIn; | |
} | |
} | |
} | |
} | |
@include slideActive { | |
transform: translateX(0); | |
transition-timing-function: ease; | |
&:before { | |
opacity: 1; | |
} | |
} | |
} | |
} | |
} | |
&__control { | |
$size: 50px; | |
z-index: 100; | |
position: absolute; | |
left: 50px; | |
top: 50%; | |
width: $size; | |
height: $size; | |
margin-top: $size/-2; | |
border-radius: 50%; | |
background: rgba(255,255,255,0.4); | |
transform: translateX($size*-1); | |
opacity: 0; | |
transition: $sliderReadyTrans; | |
cursor: pointer; | |
&:before { | |
content: ''; | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
width: 20px; | |
height: 20px; | |
margin-left: -10px; | |
margin-top: -10px; | |
border: 2px solid #000; | |
border-bottom: none; | |
border-right: none; | |
transform: translateX(5px) rotate(-45deg); | |
} | |
&--right { | |
left: auto; | |
right: 50px; | |
transform: translateX($size); | |
&:before { | |
transform: translateX(-5px) rotate(135deg); | |
} | |
} | |
@include sliderReady { | |
transform: translateX(0); | |
opacity: 1; | |
} | |
} | |
} | |
.icon-link { | |
z-index: 100; | |
position: absolute; | |
left: 5px; | |
bottom: 5px; | |
width: 32px; | |
img { | |
width: 100%; | |
vertical-align: top; | |
} | |
&--twitter { | |
left: auto; | |
right: 5px; | |
} | |
} |