在视口中动画计数器
Animate counter when in viewport
我有一个计数器,它以 HTML 中定义的最终数字为动画。但是我希望这个动画在它进入视口后就发生。
我有一个 fiddle here 显示滚动如何影响计数器编号。
$(document).ready(function() {
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i, el) {
function visPx() {
var H = $(this).height(),
r = el.getBoundingClientRect(),
t = r.top,
b = r.bottom;
return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$(".fig-number").inViewport(function(px) {
$(this).each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
});
});
我已经尝试了很多东西,但我似乎无法实现我所追求的目标。
$(document).ready(function() {
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i, el) {
function visPx() {
var H = $(this).height(),
r = el.getBoundingClientRect(),
t = r.top,
b = r.bottom;
return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$(".fig-number").inViewport(function(px) {
$(this).each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
});
});
html,
body {
height: 100%;
}
#upper-push {
height: 100%;
width: 100%;
display: block;
background: red;
color: white;
}
<div id="upper-push">
Scroll down
</div>
<div id="numbers">
<span class="fig-number">25</span>
<span class="fig-number">78</span>
</div>
如果您不介意更改代码,这可以解决问题。
jsfiddle
var $findme = $('#numbers');
var exec = false;
function Scrolled() {
$findme.each(function() {
var $section = $(this),
findmeOffset = $section.offset(),
findmeTop = findmeOffset.top,
findmeBottom = $section.height() + findmeTop,
scrollTop = $(document).scrollTop(),
visibleBottom = window.innerHeight,
prevVisible = $section.prop('_visible');
if ((findmeTop > scrollTop + visibleBottom) ||
findmeBottom < scrollTop) {
visible = false;
} else visible = true;
if (!prevVisible && visible) {
if(!exec){
$('.fig-number').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function(now) {
$(this).text(Math.ceil(now));
exec = true;
}
});
});
}
}
$section.prop('_visible', visible);
});
}
function Setup() {
var $top = $('#top'),
$bottom = $('#bottom');
$top.height(500);
$bottom.height(500);
$(window).scroll(function() {
Scrolled();
});
}
$(document).ready(function() {
Setup();
});
html,
body {
height: 100%;
}
#upper-push {
height: 100%;
width: 100%;
display: block;
background: red;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="upper-push">
Scroll down
</div>
<div id="numbers">
<span class="fig-number">25</span>
<span class="fig-number">78</span>
</div>
片段:
(function($) {
$.fn.visible = function(partial, hidden) {
var $t = $(this).eq(0),
t = $t.get(0),
$w = $(window),
viewTop = $w.scrollTop(),
viewBottom = viewTop + $w.height(),
_top = $t.offset().top,
_bottom = _top + $t.height(),
compareTop = partial === true ? _bottom : _top,
compareBottom = partial === true ? _top : _bottom,
clientSize = hidden === true ? t.offsetWidth * t.offsetHeight : true;
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop));
};
})(jQuery);
// Scrolling Functions
$(window).scroll(function(event) {
function padNum(num) {
if (num < 10) {
return "" + num;
}
return num;
}
var first = parseInt($('.c1').text());
var second = parseInt($('.c2').text());
function countStuffUp(points, selector, duration) { //Animate count
$({
countNumber: $(selector).text()
}).animate({
countNumber: points
}, {
duration: duration,
easing: 'linear',
step: function() {
$(selector).text(padNum(parseInt(this.countNumber)));
},
complete: function() {
$(selector).text(points);
}
});
}
// Output to first-count
$(".first-count").each(function(i, el) {
var el = $(el);
if (el.visible(true)) {
countStuffUp(first, '.first-count', 1600);
}
});
// Output to second count
$(".second-count").each(function(i, el) {
var el = $(el);
if (el.visible(true)) {
countStuffUp(second, '.second-count', 1000);
}
});
});
.block {
height: 1000px;
background: #eeeeee;
}
.dontShow {
//display:none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div class="block">Scroll down to bottom to see counter</div>
<div>
<span class="first-count">0</span>
<span class="second-count">0</span>
</div>
<div class="dontShow">
Max Value of count 1 : <span class="c1">25</span>
<br />Max Value of count 2 : <span class="c2">78</span>
</div>
参考:Similar
.inViewport() plugin 触发每个滚动事件的回调。
这是设计使然。 (有助于在代码中保留插件的源代码!;))
在"plugin page"你可以看到如何使用它:
$("selector").inViewport(function(px) {
console.log( px ); // `px` represents the amount of visible height
if(px){
// do this if element enters the viewport // px > 0
}else{
// do that if element exits the viewport // px = 0
}
}); // Here you can chain other jQuery methods to your selector
这意味着:
- 您必须监听
px
参数是否大于 0
(元素在视口中)
- 为了防止链接额外的动画产生堆积,你应该使用标志变量
- (不需要回调中的
$(this).each()
。插件已经对元素集合进行了操作。)
jQuery(function($) { // DOM ready and $ in scope
$(".fig-number").inViewport(function(px) {
// if px>0 (entered V.port) and
// if prop initNumAnim flag is not yet set = Animate numbers
if(px>0 && !this.initNumAnim) {
this.initNumAnim = true; // Set flag to true to prevent re-running the same animation
// <<< DO SOME COOL STUFF HERE!
}
});
});
片段示例:
// inViewport jQuery plugin
//
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el){
function visPx(){
var H = $(this).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? H-t : (b<H?b:H)));
} visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
jQuery(function($) { // DOM ready and $ in scope
$(".fig-number").inViewport(function(px) { // Make use of the `px` argument!!!
// if element entered V.port ( px>0 ) and
// if prop initNumAnim flag is not yet set
// = Animate numbers
if(px>0 && !this.initNumAnim) {
this.initNumAnim = true; // Set flag to true to prevent re-running the same animation
$(this).prop('Counter',0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function (now) {
$(this).text(Math.ceil(now));
}
});
}
});
});
html,
body {
height:100%;
}
#upper-push {
height:100%;
width:100%;
display:block;
background:red;
color:white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="upper-push">
Scroll down
</div>
<div id="numbers">
<span class="fig-number">25</span>
<span class="fig-number">78</span>
</div>
这是我的解决方案,它使用 IntersectionObserver 并且只在进入视口时设置一次动画。支持可配置的持续时间和浮动。
const initAnimatedCounts = () => {
const ease = (n) => {
// https://github.com/component/ease/blob/master/index.js
return --n * n * n + 1;
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Once this element is in view and starts animating, remove the observer,
// because it should only animate once per page load.
observer.unobserve(entry.target);
const countToString = entry.target.getAttribute('data-countTo');
const countTo = parseFloat(countToString);
const duration = parseFloat(entry.target.getAttribute('data-animateDuration'));
const countToParts = countToString.split('.');
const precision = countToParts.length === 2 ? countToParts[1].length : 0;
const startTime = performance.now();
const step = (currentTime) => {
const progress = Math.min(ease((currentTime - startTime) / duration), 1);
entry.target.textContent = (progress * countTo).toFixed(precision);
if (progress < 1) {
animationFrame = window.requestAnimationFrame(step);
} else {
window.cancelAnimationFrame(animationFrame);
}
};
let animationFrame = window.requestAnimationFrame(step);
}
});
});
document.querySelectorAll('[data-animateDuration]').forEach((target) => {
target.setAttribute('data-countTo', target.textContent);
target.textContent = '0';
observer.observe(target);
});
};
initAnimatedCounts();
div {
font-size: 30px;
text-align: center;
padding: 30px 0;
}
div > span {
color: #003d82;
}
div.scrollpad {
height: 100vh;
background-color: #eee;
}
<div>
<span>$<span data-animateDuration="1000">987.45</span></span> was spent on
about <span><span data-animateDuration="1000">5.8</span>M</span> things.
</div>
<div class="scrollpad">keep scrolling</div>
<div>
There are <span><span data-animateDuration="1000">878</span>K</span> people involved.
<br/>
And <span><span data-animateDuration="1000">54</span></span> cakes.
</div>
<div class="scrollpad">keep scrolling</div>
<div>
Additionally, <span>$<span data-animateDuration="3000">300</span>B</span> went to waste.
<br/>
Because <span>$<span data-animateDuration="2000">54</span></span> was spent on each cake.
</div>
<div class="scrollpad">keep scrolling</div>
<div>
Lastly, <span><span data-animateDuration="4000">3.5334583</span>T</span> ants said hello.
<br/>
But <span><span data-animateDuration="2000">4</span></span> of them said goodbye.
</div>
我有一个计数器,它以 HTML 中定义的最终数字为动画。但是我希望这个动画在它进入视口后就发生。
我有一个 fiddle here 显示滚动如何影响计数器编号。
$(document).ready(function() {
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i, el) {
function visPx() {
var H = $(this).height(),
r = el.getBoundingClientRect(),
t = r.top,
b = r.bottom;
return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$(".fig-number").inViewport(function(px) {
$(this).each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
});
});
我已经尝试了很多东西,但我似乎无法实现我所追求的目标。
$(document).ready(function() {
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i, el) {
function visPx() {
var H = $(this).height(),
r = el.getBoundingClientRect(),
t = r.top,
b = r.bottom;
return cb.call(el, Math.max(0, t > 0 ? H - t : (b < H ? b : H)));
}
visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$(".fig-number").inViewport(function(px) {
$(this).each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
});
});
html,
body {
height: 100%;
}
#upper-push {
height: 100%;
width: 100%;
display: block;
background: red;
color: white;
}
<div id="upper-push">
Scroll down
</div>
<div id="numbers">
<span class="fig-number">25</span>
<span class="fig-number">78</span>
</div>
如果您不介意更改代码,这可以解决问题。 jsfiddle
var $findme = $('#numbers');
var exec = false;
function Scrolled() {
$findme.each(function() {
var $section = $(this),
findmeOffset = $section.offset(),
findmeTop = findmeOffset.top,
findmeBottom = $section.height() + findmeTop,
scrollTop = $(document).scrollTop(),
visibleBottom = window.innerHeight,
prevVisible = $section.prop('_visible');
if ((findmeTop > scrollTop + visibleBottom) ||
findmeBottom < scrollTop) {
visible = false;
} else visible = true;
if (!prevVisible && visible) {
if(!exec){
$('.fig-number').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function(now) {
$(this).text(Math.ceil(now));
exec = true;
}
});
});
}
}
$section.prop('_visible', visible);
});
}
function Setup() {
var $top = $('#top'),
$bottom = $('#bottom');
$top.height(500);
$bottom.height(500);
$(window).scroll(function() {
Scrolled();
});
}
$(document).ready(function() {
Setup();
});
html,
body {
height: 100%;
}
#upper-push {
height: 100%;
width: 100%;
display: block;
background: red;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="upper-push">
Scroll down
</div>
<div id="numbers">
<span class="fig-number">25</span>
<span class="fig-number">78</span>
</div>
片段:
(function($) {
$.fn.visible = function(partial, hidden) {
var $t = $(this).eq(0),
t = $t.get(0),
$w = $(window),
viewTop = $w.scrollTop(),
viewBottom = viewTop + $w.height(),
_top = $t.offset().top,
_bottom = _top + $t.height(),
compareTop = partial === true ? _bottom : _top,
compareBottom = partial === true ? _top : _bottom,
clientSize = hidden === true ? t.offsetWidth * t.offsetHeight : true;
return !!clientSize && ((compareBottom <= viewBottom) && (compareTop >= viewTop));
};
})(jQuery);
// Scrolling Functions
$(window).scroll(function(event) {
function padNum(num) {
if (num < 10) {
return "" + num;
}
return num;
}
var first = parseInt($('.c1').text());
var second = parseInt($('.c2').text());
function countStuffUp(points, selector, duration) { //Animate count
$({
countNumber: $(selector).text()
}).animate({
countNumber: points
}, {
duration: duration,
easing: 'linear',
step: function() {
$(selector).text(padNum(parseInt(this.countNumber)));
},
complete: function() {
$(selector).text(points);
}
});
}
// Output to first-count
$(".first-count").each(function(i, el) {
var el = $(el);
if (el.visible(true)) {
countStuffUp(first, '.first-count', 1600);
}
});
// Output to second count
$(".second-count").each(function(i, el) {
var el = $(el);
if (el.visible(true)) {
countStuffUp(second, '.second-count', 1000);
}
});
});
.block {
height: 1000px;
background: #eeeeee;
}
.dontShow {
//display:none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div class="block">Scroll down to bottom to see counter</div>
<div>
<span class="first-count">0</span>
<span class="second-count">0</span>
</div>
<div class="dontShow">
Max Value of count 1 : <span class="c1">25</span>
<br />Max Value of count 2 : <span class="c2">78</span>
</div>
参考:Similar
.inViewport() plugin 触发每个滚动事件的回调。
这是设计使然。 (有助于在代码中保留插件的源代码!;))
在"plugin page"你可以看到如何使用它:
$("selector").inViewport(function(px) {
console.log( px ); // `px` represents the amount of visible height
if(px){
// do this if element enters the viewport // px > 0
}else{
// do that if element exits the viewport // px = 0
}
}); // Here you can chain other jQuery methods to your selector
这意味着:
- 您必须监听
px
参数是否大于0
(元素在视口中) - 为了防止链接额外的动画产生堆积,你应该使用标志变量
- (不需要回调中的
$(this).each()
。插件已经对元素集合进行了操作。)
jQuery(function($) { // DOM ready and $ in scope
$(".fig-number").inViewport(function(px) {
// if px>0 (entered V.port) and
// if prop initNumAnim flag is not yet set = Animate numbers
if(px>0 && !this.initNumAnim) {
this.initNumAnim = true; // Set flag to true to prevent re-running the same animation
// <<< DO SOME COOL STUFF HERE!
}
});
});
片段示例:
// inViewport jQuery plugin
//
$(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el){
function visPx(){
var H = $(this).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? H-t : (b<H?b:H)));
} visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
jQuery(function($) { // DOM ready and $ in scope
$(".fig-number").inViewport(function(px) { // Make use of the `px` argument!!!
// if element entered V.port ( px>0 ) and
// if prop initNumAnim flag is not yet set
// = Animate numbers
if(px>0 && !this.initNumAnim) {
this.initNumAnim = true; // Set flag to true to prevent re-running the same animation
$(this).prop('Counter',0).animate({
Counter: $(this).text()
}, {
duration: 1000,
step: function (now) {
$(this).text(Math.ceil(now));
}
});
}
});
});
html,
body {
height:100%;
}
#upper-push {
height:100%;
width:100%;
display:block;
background:red;
color:white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="upper-push">
Scroll down
</div>
<div id="numbers">
<span class="fig-number">25</span>
<span class="fig-number">78</span>
</div>
这是我的解决方案,它使用 IntersectionObserver 并且只在进入视口时设置一次动画。支持可配置的持续时间和浮动。
const initAnimatedCounts = () => {
const ease = (n) => {
// https://github.com/component/ease/blob/master/index.js
return --n * n * n + 1;
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Once this element is in view and starts animating, remove the observer,
// because it should only animate once per page load.
observer.unobserve(entry.target);
const countToString = entry.target.getAttribute('data-countTo');
const countTo = parseFloat(countToString);
const duration = parseFloat(entry.target.getAttribute('data-animateDuration'));
const countToParts = countToString.split('.');
const precision = countToParts.length === 2 ? countToParts[1].length : 0;
const startTime = performance.now();
const step = (currentTime) => {
const progress = Math.min(ease((currentTime - startTime) / duration), 1);
entry.target.textContent = (progress * countTo).toFixed(precision);
if (progress < 1) {
animationFrame = window.requestAnimationFrame(step);
} else {
window.cancelAnimationFrame(animationFrame);
}
};
let animationFrame = window.requestAnimationFrame(step);
}
});
});
document.querySelectorAll('[data-animateDuration]').forEach((target) => {
target.setAttribute('data-countTo', target.textContent);
target.textContent = '0';
observer.observe(target);
});
};
initAnimatedCounts();
div {
font-size: 30px;
text-align: center;
padding: 30px 0;
}
div > span {
color: #003d82;
}
div.scrollpad {
height: 100vh;
background-color: #eee;
}
<div>
<span>$<span data-animateDuration="1000">987.45</span></span> was spent on
about <span><span data-animateDuration="1000">5.8</span>M</span> things.
</div>
<div class="scrollpad">keep scrolling</div>
<div>
There are <span><span data-animateDuration="1000">878</span>K</span> people involved.
<br/>
And <span><span data-animateDuration="1000">54</span></span> cakes.
</div>
<div class="scrollpad">keep scrolling</div>
<div>
Additionally, <span>$<span data-animateDuration="3000">300</span>B</span> went to waste.
<br/>
Because <span>$<span data-animateDuration="2000">54</span></span> was spent on each cake.
</div>
<div class="scrollpad">keep scrolling</div>
<div>
Lastly, <span><span data-animateDuration="4000">3.5334583</span>T</span> ants said hello.
<br/>
But <span><span data-animateDuration="2000">4</span></span> of them said goodbye.
</div>