쪼렙 as! 풀스택

18.10.05 리액트로 smooth scroll 되는 슬라이더 만들기. 본문

개발 일지/Web & Server

18.10.05 리액트로 smooth scroll 되는 슬라이더 만들기.

코코앱 2018. 10. 5. 17:17



Netflix 비슷한 슬라이더를 만들어야 했다.


smooth 스크롤을 구현해야 했는데, 검색해보니 여러가지 방법이 있었는데,

편하게 쓸 수 있는 것 같은 방법들은 대부분 Edge 까지 지원하질 못했다.


그래서 표준 Javascript 로만 직접 만들기로 했다.


아래 코드는, 지금 진행하고 있는 프로젝트에만 적용되는 코드이긴 한데,

시간이 나면 smoothScroll 하는 라이브러리로 오픈소스로 만들어야겠다.





import {Component} from 'react'
import '../styles/Home.scss'

//스크롤 애니메이션 0.5초.
const ANIM_DURATION = 300
const FRAME_TIME = 10

export default class VideoSlider extends Component {

constructor(props){
super(props)
this.mSeriesData = props.seriesData
this.mSliderRef = React.createRef();
this.state = {isHideLeft:true, isHideRight: false}
this.mScrolled = 0;
}

componentDidMount() {
this.refreshButtonHide()
}

//슬라이더 내부의 양옆 패딩 값을 가져오기.
getSliderPadding() {
return this.mSliderRef.current.firstChild.offsetLeft
}

//페이징할 스크롤 양을 계산하기. 양옆에 padding 만큼을 빼준다.
getPageSize() {
const padding = this.getSliderPadding() * 2;
return this.mSliderRef.current.offsetWidth - padding
}

//진행률에 따라 속도를 늦춰주는 양을 계산하기.
getRecorrectScrollByProgress(stepSize, pageSize, scrolled){
const progress = scrolled / pageSize
if(progress > 0.98) { return Math.ceil(stepSize * 0.02) }
else if(progress > 0.96) { return Math.ceil(stepSize * 0.06) }
else if(progress > 0.93) { return Math.ceil(stepSize * 0.12) }
else if(progress > 0.9) { return Math.ceil(stepSize * 0.2) }
else if(progress > 0.8) { return Math.ceil(stepSize * 0.4) }
else if(progress > 0.7) { return Math.ceil(stepSize * 0.6) }
else if(progress > 0.6) { return Math.ceil(stepSize * 0.8) }
else {
return stepSize
}
}



// 오른쪽으로 버튼.
clickRight = () => {

//우측으로 총 이동해야 할 값
let pageSize = this.getPageSize()
//스크롤할 수 있는 최대 한도
const limit = this.mSliderRef.current.scrollWidth - this.mSliderRef.current.offsetWidth
//pageSize 만큼 이동했을 때, 위치 예상값.
let target = pageSize + this.mSliderRef.current.scrollLeft
//만약, 예상값이 liimt 보다 크다면, pageSize 에서 그 차이만큼 빼준다.
if(target > limit) {
// console.log("한도보다 목표값이 더 커서 pageSize 를 조정한다.")
const dist = target - limit
pageSize = pageSize - dist
target = limit
console.log("보정된 pageSize ", pageSize)
}

//pageSize 를 프레임으로 나눠서, 한프레임에 얼만큼 이동해야할지 계산한다.
const stepSize = Math.ceil(pageSize / (ANIM_DURATION / FRAME_TIME))
console.log("프레임 시간 당 움직일값. ", stepSize)

//mScrolled 를 초기화한다.
this.mScrolled = 0;
this.smoothScrollToRight(stepSize, target, pageSize)
}

smoothScrollToRight(stepSize, target, pageSize) {

//아직 목표치까지 도달하지 않았다면, 이동해준다.
if(this.mSliderRef.current.scrollLeft < target) {
const scroll = this.getRecorrectScrollByProgress(stepSize, pageSize, this.mScrolled)
const posX = this.mSliderRef.current.scrollLeft + scroll
// this.mSliderRef.current.scrollTo(posX, 0);
this.mSliderRef.current.scrollLeft = posX
this.mScrolled += scroll

//재귀함수 호출.
setTimeout(() => {
this.smoothScrollToRight(stepSize, target, pageSize)
}, FRAME_TIME);
}else{
//목표에 도달했다면, 좌우 버튼을 refresh
this.refreshButtonHide()
}
}

clickLeft = () => {

//좌측으로 총 이동해야 할 값은?
let pageSize = 0 - this.getPageSize()
//스크롤할 수 있는 최대 한도
const limit = 0;
//pageSize 만큼 이동했을 때, 위치 예상값.
let target = pageSize + this.mSliderRef.current.scrollLeft
//만약, 예상값이 liimt 보다 적다면, limit 로 보정해준다.
if(target < limit) {
const dist = limit - target
pageSize = pageSize + dist
target = limit
}

//프레임당 이동해야할 양 계산. 소수점 올림을 했는데, 좌측으로 이동해야 하므로, -1 해준다.
const stepSize = Math.ceil(pageSize / (ANIM_DURATION / FRAME_TIME)) - 1
this.mScrolled = 0;
this.smoothScrollToLeft(stepSize, target, pageSize)
}

smoothScrollToLeft(stepSize, target, pageSize) {
//아직 목표까지 도달하지 않았다면, 이동해준다.
if(this.mSliderRef.current.scrollLeft > target) {

// 이때 얻어온 scroll은 반올림한것이므로 -1 해준다.
const scroll = this.getRecorrectScrollByProgress(stepSize, pageSize, this.mScrolled) - 1
const posX = this.mSliderRef.current.scrollLeft + scroll
// this.mSliderRef.current.scrollTo(posX, 0);
this.mSliderRef.current.scrollLeft = posX
this.mScrolled += scroll

//재귀함수 호출.
setTimeout(() => {
this.smoothScrollToLeft(stepSize, target, pageSize)
}, FRAME_TIME);

}else{
//도달했다면, 버튼 refresh
this.refreshButtonHide()
}
}

refreshButtonHide() {
const scrollLeft = this.mSliderRef.current.scrollLeft
const isHideLeft = scrollLeft === 0
const isHideRight = (scrollLeft + this.mSliderRef.current.offsetWidth) >= this.mSliderRef.current.scrollWidth
this.setState({isHideLeft:isHideLeft, isHideRight: isHideRight})
}




render() {

const leftButtonStyle = this.state.isHideLeft ? {display:'none'} : {left:'0'}
const rightButtonStyle = this.state.isHideRight ? {display:'none'} : {right:'0'}

return (
<div className="VideoSlider">
<div className="wrap-series-videos padding-wrapper" ref={this.mSliderRef} >
{this.mSeriesData.videos.map((videoData, index) => (
<VideoItem key={index} data={videoData} />
))}
</div>

{/* 하단에 스크롤바 숨기는 div */}
<div className="hide-scroll" />

{/* 우 슬라이더 버튼. */}
<button onClick={this.clickRight} className="slider-btn" style={rightButtonStyle} ></button>
{/* 좌 슬라이더 버튼. */}
<button onClick={this.clickLeft} className="slider-btn" style={leftButtonStyle} ></button>
</div>
)
}
}



const VideoItem = ({data}) => {
return (
<div className="VideoSliderItem">
<img className="img-video-cover" src={data.coverUrl} />
</div>
)
}


Comments