给回到顶部滚动加动画

380 阅读8分钟
原文链接: zhuanlan.zhihu.com

JS实现jQuery的animate()动画

移动端:document.body.scrollTop

PC端:document.documentElement.scrollTop

var GAP = 0;  //距离文档顶部距离预设,默认为0
var scrollToTop = function(scrollDuration) {
	var scrollHeight = window.pageYOffset;
	var scrollStep = Math.PI / (scrollDuration / 15);
	var cosParameter = scrollHeight / 2;
	var scrollCount = 0;
	var scrollMargin;
	var scrollPosition;

	var scrollInterval = setInterval(function() {
		if(window.pageYOffset != 0) {
			scrollCount = scrollCount + 1;
			scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);

			if(scrollHeight - scrollMargin > scrollPosition) {
				clearInterval(scrollInterval);
				scrollToTop(scrollDuration / scrollCount);
			}

			scrollPosition = scrollHeight - scrollMargin;

			window.scrollTo(0, (scrollPosition));
		} else {
			clearInterval(scrollInterval);
		}
	}, 15);
};

var scrollToElement = function(element, interval) {
	var offset = getElementOffset(element);   //获取到文档顶部距离
	var initialOffset = window.pageYOffset;

	var scrollHeight = offset - initialOffset;

	var scrollStep = Math.PI / (interval / 15);
	var cosParameter = scrollHeight / 2;
	var scrollCount = 0;
	var scrollMargin;
	var previousScrollMargin;
	var signChanged = false;

	var scrollInterval = setInterval(function() {
		if((window.pageYOffset < offset - GAP || window.pageYOffset > offset) && !signChanged) {
			scrollCount = scrollCount + 1;
			scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);

			if(previousScrollMargin) {
				signChanged = Math.abs(scrollMargin) < Math.abs(previousScrollMargin);
			}

			window.scrollTo(0, initialOffset + scrollMargin);
			previousScrollMargin = scrollMargin;
		} else {
			clearInterval(scrollInterval);
		}
	}, 15);
};

var getElementOffset = function(element) {
	return window.pageYOffset + element.getBoundingClientRect().top;
}

升级版:

var GAP = 0; //距离文档顶部距离预设,默认为0
export var scrollToTop = function(scrollDuration) {
	/*
	移动端需要设置滚动条请使用
         document.body.scrollTop,
     PC上使用
         document.documentElement.scrollTop
    */
	var scrollHeight = document.body.scrollTop || document.documentElement.scrollTop;
	var scrollStep = Math.PI / (scrollDuration / 15);
	var cosParameter = scrollHeight / 2;
	var scrollCount = 0;
	var scrollMargin;
	var scrollPosition;

	var scrollInterval = setInterval(function() {
		let pageYOffset = document.body.scrollTop || document.documentElement.scrollTop;
		if(pageYOffset != 0) {
			scrollCount = scrollCount + 1;
			scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);

			if(scrollHeight - scrollMargin > scrollPosition) {
				clearInterval(scrollInterval);
				scrollToTop(scrollDuration / scrollCount);
			}

			scrollPosition = scrollHeight - scrollMargin;

			document.body.scrollTop = scrollPosition
			document.documentElement.scrollTop = scrollPosition;
		} else {
			clearInterval(scrollInterval);
		}
	}, 15);
};

export var scrollToElement = function(element, interval) {
	var offset = getElementOffset(element); //获取到文档顶部距离
	var initialOffset = document.body.scrollTop || document.documentElement.scrollTop;

	var scrollHeight = offset - initialOffset;

	var scrollStep = Math.PI / (interval / 15);
	var cosParameter = scrollHeight / 2;
	var scrollCount = 0;
	var scrollMargin;
	var previousScrollMargin;
	var signChanged = false;

	var scrollInterval = setInterval(function() {
		if((document.documentElement.scrollTop < offset - GAP || document.documentElement.scrollTop > offset) && !signChanged) {
			scrollCount = scrollCount + 1;
			scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);

			if(previousScrollMargin) {
				signChanged = Math.abs(scrollMargin) < Math.abs(previousScrollMargin);
			}

			document.body.scrollTop = initialOffset + scrollMargin;
			document.documentElement.scrollTop = initialOffset + scrollMargin;
			previousScrollMargin = scrollMargin;
		} else {
			clearInterval(scrollInterval);
		}
	}, 15);
};

var getElementOffset = function(element) {
	var initialOffset = document.body.scrollTop || document.documentElement.scrollTop;
	return initialOffset + element.getBoundingClientRect().top;
}
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<style type="text/css">
			*{
				padding: 0px;
				margin: 0px;
				
			}
			body,html{
				height: 10000px;
			}
			#main{
				width: 100px;
				height: 100px;
				background: red;
			}
			#text{
				margin-top: 1000px;
				width: 100px;
				height: 100px;
				background: red;
			}
		</style>
		<script src="./index.js"></script>
		<script type="text/javascript">
			window.onload=function(){
				setTimeout(()=>{
                                        scrollToTop(1000);   //回到顶部
					scrollToElement(document.querySelector("#text"),5500);
				},2000)
				
			}
		</script>
	</head>
	<body>
		
		<div id="main">
			
		</div>
		
		
		<div id="text">
			
		</div>
	</body>
</html>

推荐用以下这个:

(function(window) {
	var requestAnimFrame = (function() {
		return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) {
			window.setTimeout(callback, 1000 / 60);
		};
	})();

	var easeInOutQuad = function(t, b, c, d) {
		t /= d / 2;
		if(t < 1) return c / 2 * t * t + b;
		t--;
		return -c / 2 * (t * (t - 2) - 1) + b;
	};

	var animatedScrollTo = function(element, to, duration, callback) {
		var start = element.scrollTop,
			change = to - start,
			animationStart = +new Date();
		var animating = true;
		var lastpos = null;

		var animateScroll = function() {
			if(!animating) {
				return;
			}
			requestAnimFrame(animateScroll);
			var now = +new Date();
			var val = Math.floor(easeInOutQuad(now - animationStart, start, change, duration));
			console.log(val)
			if(lastpos) {
				if(lastpos === element.scrollTop) {
					lastpos = val;
					element.scrollTop = val;
				} else {
					animating = false;
				}
			} else {
				lastpos = val;
				element.scrollTop = val;
			}
			if(now > animationStart + duration) {
				element.scrollTop = to;
				animating = false;
				if(callback) {
					callback();
				}
			}
		};
		requestAnimFrame(animateScroll);
	};

	if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
		module.exports = animatedScrollTo;
	} else {
		window.animatedScrollTo = animatedScrollTo;
	}
})(window);

增加几种动画效果:

var requestAnimFrame = (function() {
	return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) {
		window.setTimeout(callback, 1000 / 60);
	};
})();

var tween = {
	linear: function(t, b, c, d) {
		return c * t / d + b;
	},
	easeIn: function(t, b, c, d) {
		return c * (t /= d) * t + b;
	},
	strongEaseIn: function(t, b, c, d) {
		return c * (t /= d) * t * t * t * t + b;
	},
	strongEaseOut: function(t, b, c, d) {
		return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
	},
	sineaseIn: function(t, b, c, d) {
		return c * (t /= d) * t * t + b;
	},
	sineaseOut: function(t, b, c, d) {
		return c * ((t = t / d - 1) * t * t + 1) + b;
	},
	easeInOutQuad: function(t, b, c, d) {
		t /= d / 2;
		if(t < 1) return c / 2 * t * t + b;
		t--;
		return -c / 2 * (t * (t - 2) - 1) + b;
	}
};

export var animatedScrollTo = function(element, to, duration,callback, Bzr='linear') {
	var start = element.scrollTop,
		change = to - start,
		animationStart = +new Date();
	var animating = true;
	var lastpos = null;

	var animateScroll = function() {
		if(!animating) {
			return;
		}
		requestAnimFrame(animateScroll);
		var now = +new Date();
		var val = Math.floor(tween[Bzr](now - animationStart, start, change, duration));

		if(lastpos) {
			if(lastpos === element.scrollTop) {
				lastpos = val;
				element.scrollTop = val;
			} else {
				animating = false;
			}
		} else {
			lastpos = val;
			element.scrollTop = val;
		}
		if(now > animationStart + duration) {
			element.scrollTop = to;
			animating = false;
			if(callback) {
				callback();
			}
		}
	};
	requestAnimFrame(animateScroll);
};

增加清除定时器:

var requestAnimFrame = (function() {
	return window.requestAnimationFrame ||
		window.webkitRequestAnimationFrame ||
		window.mozRequestAnimationFrame ||
		function(callback) {
			window.setTimeout(callback, 1000 / 60);
		};
})();
var cancelAnimationFrame = (function() {
	return window.cancelAnimationFrame ||
		window.webkitCancelAnimationFrame ||
		window.mozCancelAnimationFrame ||
		window.oCancelAnimationFrame ||
		function(id) {
			window.clearTimeout(id)
		}
})()

var tween = {
	linear: function(t, b, c, d) {
		return c * t / d + b;
	},
	easeIn: function(t, b, c, d) {
		return c * (t /= d) * t + b;
	},
	strongEaseIn: function(t, b, c, d) {
		return c * (t /= d) * t * t * t * t + b;
	},
	strongEaseOut: function(t, b, c, d) {
		return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
	},
	sineaseIn: function(t, b, c, d) {
		return c * (t /= d) * t * t + b;
	},
	sineaseOut: function(t, b, c, d) {
		return c * ((t = t / d - 1) * t * t + 1) + b;
	},
	easeInOutQuad: function(t, b, c, d) {
		t /= d / 2;
		if(t < 1) return c / 2 * t * t + b;
		t--;
		return -c / 2 * (t * (t - 2) - 1) + b;
	}
};
var myReq; 
export var animatedScrollTo = function(element, to, duration, callback, Bzr = 'linear') {
	var start = element.scrollTop,
		change = to - start,
		animationStart = +new Date();
	var animating = true;
	var lastpos = null;

	var animateScroll = function() {
		console.log('-------------')
		if(!animating) {
			return;
		}
		myReq=requestAnimFrame(animateScroll);
		var now = +new Date();
		var val = Math.floor(tween[Bzr](now - animationStart, start, change, duration));

		if(lastpos) {
			if(lastpos === element.scrollTop) {
				lastpos = val;
				element.scrollTop = val;
			} else {
				animating = false;
			}
		} else {
			lastpos = val;
			element.scrollTop = val;
		}
		if(now > animationStart + duration) {
			element.scrollTop = to;
			animating = false;
			cancelAnimationFrame(myReq);   //清除定时器动画
			callback&&callback();
		}
	};
	myReq=requestAnimFrame(animateScroll);
};
<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<style type="text/css">
			* {
				padding: 0px;
				margin: 0px;
			}
			
			body,
			html {
				height: 10000px;
			}
			
			#main {
				width: 100px;
				height: 100px;
				background: red;
			}
			
			#text {
				margin-top: 1000px;
				width: 100px;
				height: 100px;
				background: red;
			}
		</style>
		<script src="animatedScrollTo.js"></script>
		<script type="text/javascript">
			window.onload = function() {

				setTimeout(() => {
					animatedScrollTo(
						document.documentElement, // element to scroll with (most of times you want to scroll with whole <body>)
						2000, // target scrollY (0 means top of the page)
						700, // duration in ms
						function() { // callback function that runs after the animation (optional)
							console.log('done!')
						}
					);
				}, 2000)

			}
		</script>
	</head>

	<body>

		<div id="main">

		</div>

		<div id="text">

		</div>
	</body>

</html>

Vue DEMO:

<template>
	<div>
		<div class="cardList" v-for="(item,index) in lists">
			<div class="list onepx-top-border" id="listCon">
				<div @click="jump(item.link)" class="lg">
					<img v-if="item.poster_image" class="banner" :src="item.poster_image" />
					<img v-else class="banner" :src="bgimg" />
					<div class="min"></div>
					<div class="nu">{{index+1}}</div>
				</div>
				<div class="mvcont flex" @click.stop="jump(item.link)">
					<p class="mvnema">{{item.name}}</p>
					<p class="performer" v-if="item.actor">演员:{{item.actor}}</p>
					<p class="performer" v-else>&nbsp;</p>
					<div class="details">
						<img class="fire" src="../../assets/img/fire@3x.png" />
						<span class="hot">热度 {{item.hot_score}}</span>
						<span class="data" @click.stop="toggleChars(index)">数据详情</span>
						<span class="arrow" :class="{ active: showIndex==index}"></span>
					</div>
				</div>
				<div class="hband" @click="jump(item.button_info.url)">
					<div class="hit">{{item.button_info.text}}</div>
				</div>
			</div>
			<div id="datadetails" class="datadetails" v-if="showIndex==index">
				<div class="con onepx-top-border">
					<div class="left">
						<div class="num color-636363">视频播放 排名<span class="yll">{{item.video_play.play_rank}}</span>/<span>{{item.video_play.play_total}}</span></div>
						<div class="palys flex">
							<div class="playnum">
								<div class="num" :style="{width: item.video_play.play_score+'%'}"></div>
							</div>
							<span class="color-636363">{{item.video_play.play_count}}</span>
						</div>
						<div class="num color-636363 maginTop">视频发布 排名<span class="yll">{{item.video_publish.publish_rank}}</span>/<span>{{item.video_publish.publish_total}}</span></div>
						<div class="palys flex">
							<div class="playnum">
								<div class="num" :style="{width: item.video_publish.publish_score+'%'}"></div>
							</div>
							<span class="color-636363">{{item.video_publish.publish_count}}</span>
						</div>
					</div>
					<div v-if="item.video_image" class="right" :style="{backgroundImage: 'url('+item.video_image+')'}" @click="jump(item.video_link)">
						<div class="palyicon"></div>
					</div>
					<div v-else class="right" :style="{backgroundImage: 'url('+bgimg+')'}" @click="jump(item.video_link)">
					</div>
				</div>
				<div>
					<chart :day='day(item.rank_data_day)' :week='week(item.rank_data_week)'></chart>
				</div>
			</div>
		</div>
	</div>
</template>

<script>
	import Toast from 'mint-ui/lib/toast';
	import 'mint-ui/lib/toast/style.css';

	import Bus from "@/common/openScheme.js"
	import chart from "@/components/pm/chart.vue"
	import bgimg from "@/assets/img/none.png"
	import {animatedScrollTo} from "@/utils/scrollTo"

	export default {
		props: ['lists'],
		data() {
			return {
				bgimg: bgimg,
				showIndex: -1 //当前显示chart的card
			}
		},
		components: {
			chart
		},
		computed: {

		},
		mounted: function() {
			this.toggleChars(0);
		},
		methods: {
			getPosition(node) {
				var width = node.offsetWidth; //元素宽度
				var height = node.offsetHeight; //元素高度
				var left = node.offsetLeft; //获取元素相对于其根元素的left值var left
				var top = node.offsetTop; //获取元素相对于其根元素的top值var top
				var current = node.offsetParent; // 取得元素的offsetParent

				// 一直循环直到根元素  
				while(current != null) {  
					left += current.offsetLeft;  
					top += current.offsetTop;  
					current = current.offsetParent;  
				}
				return {
					"width": width,
					"height": height,
					"left": left,
					"top": top
				};
			},
			toggleChars(index) {
				if(this.showIndex == index) {
					this.showIndex = -1;
				} else {
					this.showIndex = -1;
					this.$nextTick(() => {
						var H=0;
						if(document.getElementsByClassName("cardList").length){
							 H =this.getPosition(document.getElementsByClassName("cardList")[index]).top;
						}
						setTimeout(()=>{
							if(index==0){    //如果点击的是第一个,直接回到顶部
								H=0;
							}
							animatedScrollTo(  //移动端:document.body.scrollTop     PC端:document.documentElement.scrollTop
								document.body,H,500,function(){
									console.log('done!');
								}
							)
							//document.body.scrollTop = H;
				            //document.documentElement.scrollTop = H;
						},0)
						
				        this.showIndex = index;
					})
				}
			},
			day(data) {
				if(data) {
					let Y = data.map((item) => {
						return +item.rank_score
					});
					let X = data.map((item) => {
						let time = item.rank_time.split(" ")[1].split(":");
						return time[0] + ':' + time[1]
					});
					let Z = data.map((item) => {
						return +item.rank_rank
					});
					return {
						X,
						Y,
						Z
					}
				}
			},
			week(data) {
				if(data) {
					let Y = data.map((item) => {
						return +item.rank_score
					});
					let X = data.map((item) => {
						let time = item.rank_time.split(" ")[0].split("-");
						return time[1] + '/' + time[2]
					});
					let Z = data.map((item) => {
						return +item.rank_rank
					});
					return {
						X,
						Y,
						Z
					}
				}
			},
			jump(url) {
				Bus.$emit("openScheme", url);
				return false;
			}
		}
	}
</script>

<style lang="scss" scoped="scoped">
	.cardList {
		background: white;
		position: relative;
		.list {
			box-sizing: border-box;
			padding: .24rem .24rem;
			overflow: hidden;
			display: flex;
			justify-content: flex-start;
			align-items: center;
			.lg {
				position: relative;
				width: 1.18rem;
				height: 1.60rem;
				.banner {
					width: 1.18rem;
					height: 1.60rem;
				}
				.min {
					position: absolute;
					top: 0px;
					left: 0px;
					width: 0;
					height: 0;
					border-top: 0.5rem solid #FFD644;
					border-right: 0.5rem solid transparent;
				}
				.nu {
					position: absolute;
					font-size: 0.24rem;
					line-height: 0.28rem;
					text-align: center;
					top: 0.05rem;
					left: 0.01rem;
					width: 0.25rem;
					height: 0.25rem;
					color: black;
					transform: scale(0.9);
				}
			}
			.mvcont {
				position: relative;
				box-sizing: border-box;
				padding: 0.12rem .12rem 0rem;
				flex: 1;
				width: 100%;
				height: 1.60rem;
				margin: 0 0.05rem;
		        flex-direction: column;
		        flex-wrap: wrap;
				.mvnema {
					font-size: 0.32rem;
					color: #333333;
					padding-bottom: 0.05rem;
				}
				.performer {
					line-height: normal;
					font-size: 0.24rem;
					color: #636363;
					display: -webkit-box !important;
					overflow: hidden;
					text-overflow: ellipsis;
					word-break: break-all;
					-webkit-box-orient: vertical;
					-webkit-line-clamp: 1;
				}
				.details {
					font-size: 0.24rem;
					line-height: .27rem;
					color: #939393;
					align-self:flex-start;
					position: absolute;
					left: 0.12rem;
					bottom: 0.12rem;
					.fire {
						width: 0.2rem;
						margin-top: -0.05rem;
						vertical-align: middle;
					}
					.hot {
						display: inline-block;
						line-height: 1;
						line-height: normal;
					}
					.data {
						line-height: 1;
						display: inline-block;
						margin-left: 0.3rem;
						color: #FF8200;
						line-height: normal;
					}
					.arrow {
						display: inline-block;
						width: 0.2rem;
						height: 0.2rem;
						background-image: url("../../assets/img/jiantou.svg");
						background-repeat: no-repeat;
						background-size: 0.2rem 0.2rem;
						transition: -webkit-transform .2s ease-in-out;
						&.active {
							transform: rotate(180deg);
						}
					}
				}
			}
			.hband {
				background: #FF8200;
				border-radius: 0.04rem;
				width: 1.28rem;
				height: .58rem;
				line-height: .68rem;
				text-align: center;
				.hit {
					font-size: 0.28rem;
					color: #FFFFFF;
					letter-spacing: 0;
				}
			}
		}
		.datadetails {
			width: 100%;
			.con {
				position: relative;
				margin: 0rem .24rem;
				padding-top: 0.24rem;
				padding-bottom: 0.24rem;
				display: flex;
				justify-content: flex-start;
				align-items: center;
				.left {
					height: 1.46rem;
					flex: 1;
					font-size: 0.24rem;
					color: #636363;
					line-height: 0.24rem;
					.color-636363 {
						color: #636363;
						.yll {
							color: #ffa74b;
						}
						&.maginTop {
							margin-top: 0.3rem;
						}
					}
					.palys {
						margin: 0.1rem 0rem 0.1rem 0rem;
						vertical-align: middle;
						align-items: center;
						.playnum {
							width: 3rem;
							height: .24rem;
							background: #E4E4E4;
							display: inline-block;
							position: relative;
							.num {
								position: absolute;
								top: 0rem;
								left: 0rem;
								width: 0%;
								height: 100%;
								background: #FF8200;
							}
						}
						span {
							display: inline-block;
							margin-left: 0.1rem;
							height: .24rem;
							line-height: .24rem;
						}
					}
				}
				.right {
					display: block;
					width: 2.6rem;
					height: 1.46rem;
					background-image: url('https://wx1.sinaimg.cn/large/60ca8a58ly1fkrxoeljr3j21hc0m6go8.jpg');
					background-size: 100% 100%;
					position: relative;
					.palyicon {
						width: .64rem;
						height: .64rem;
						border: 1px solid white;
						border-radius: 50%;
						position: absolute;
						top: 50%;
						left: 50%;
						margin-left: -.32rem;
						margin-top: -.32rem;
						&::after {
							content: "";
							position: absolute;
							top: 50%;
							left: 50%;
							margin-left: -0.10rem;
							margin-top: -0.18rem;
							width: 0px;
							height: 0px;
							border-top: 0.18rem solid transparent;
							border-left: 0.24rem solid white;
							border-bottom: 0.18rem solid transparent;
						}
					}
				}
				.chars {
					width: 100%;
					.tit {
						height: 0.34rem;
						width: 100%;
						font-size: 0.24rem;
						.trend {
							float: left;
							color: #636363;
						}
						.time1,
						.time2 {
							color: white;
							width: 1.07rem;
							height: .33rem;
							line-height: .33rem;
							text-align: center;
							background: #bdbdbd;
							border-radius: 0.34rem;
							float: right;
							div {
								font-size: 0.24rem;
								width: 100%;
								height: 100%;
								transform: scale(0.7);
							}
							&.magr {
								margin-left: 0.15rem;
							}
							&.active {
								background: #FF8200;
							}
						}
					}
				}
				.charscon {
					width: 100%;
					height: 2rem;
					margin-top: 0.2rem;
					background: pink;
				}
			}
		}
	}
</style>