我有一个 AngularJS 应用程序,并且有一个平滑滚动指令来强制页面滚动到底部。我希望该命令仅在滚动完成后运行。您可以看到,此时我运行滚动功能,然后运行
$('#comment-input').focus();
来聚焦于某个元素。我想更改它,以便仅在滚动之后运行。我知道我需要实现一个 callback
但我不知道在哪里实现它。(function() {
var app = angular.module('myApp');
app.service('anchorSmoothScroll', function(){
this.scrollTo = function(eID) {
// This scrolling function
// is from http://www.itnewb.com/tutorial/Creating-the-Smooth-Scroll-Effect-with-JavaScript
var startY = currentYPosition();
var stopY = elmYPosition(eID);
var distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
scrollTo(0, stopY); return;
}
var speed = Math.round(distance / 100);
if (speed >= 20) speed = 20;
var step = Math.round(distance / 25);
var leapY = stopY > startY ? startY + step : startY - step;
var timer = 0;
if (stopY > startY) {
for ( var i=startY; i<stopY; i+=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY += step; if (leapY > stopY) leapY = stopY; timer++;
} return;
}
for ( var i=startY; i>stopY; i-=step ) {
setTimeout("window.scrollTo(0, "+leapY+")", timer * speed);
leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
}
function currentYPosition() {
// Firefox, Chrome, Opera, Safari
if (self.pageYOffset) return self.pageYOffset;
// Internet Explorer 6 - standards mode
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (document.body.scrollTop) return document.body.scrollTop;
return 0;
}
function elmYPosition(eID) {
var elm = document.getElementById(eID);
var y = elm.offsetTop;
var node = elm;
while (node.offsetParent && node.offsetParent != document.body) {
node = node.offsetParent;
y += node.offsetTop;
} return y;
}
};
});
app.controller('TextareaController', ['$scope','$location', 'anchorSmoothScroll',
function($scope, $location, anchorSmoothScroll) {
$scope.gotoElement = function (eID){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom-div');
// call $anchorScroll()
anchorSmoothScroll.scrollTo(eID);
$('#comment-input').focus();
};
}]);
}());
smoothScroll(element).then(() => {
//Execute something when scrolling has finished
});
smoothScroll
函数可以这样定义,而不需要使用很多计时器(实际定义的计时器只是为了
reject()
承诺如果由于某种原因(例如用户交互)滚动失败):function smoothScroll(elem, offset = 0) {
const rect = elem.getBoundingClientRect();
let targetPosition = Math.floor(rect.top + self.pageYOffset + offset);
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
return new Promise((resolve, reject) => {
const failed = setTimeout(() => {
reject();
}, 2000);
const scrollHandler = () => {
if (self.pageYOffset === targetPosition) {
window.removeEventListener("scroll", scrollHandler);
clearTimeout(failed);
resolve();
}
};
if (self.pageYOffset === targetPosition) {
clearTimeout(failed);
resolve();
} else {
window.addEventListener("scroll", scrollHandler);
elem.getBoundingClientRect();
}
});
}
我还在这支笔中制作了一个工作示例:https://codepen.io/familjenpersson/pen/bQeEBX
了解有关 $q 的更多信息。
anchorSmoothScroll.scrollTo(eID)
.then(function() {
$('#comment-input').focus();
})
编辑:您的代码失败了,因为您正在 setTimeout 内执行scrollTo,而这就是您必须解决您的承诺的地方。现在,下一个问题是有多个 setTimeouts(因为它在 for 循环内),因此会有很多承诺,而这正是 $q.all 可以提供帮助的地方。
我已经为你设置了一个plunker,我在其中更新了其中一个执行路径...看看
这里。在控制台窗口中,滚动完成后您将看到焦点是打印机。为了使其显而易见,我将 setTimeout 间隔硬编码为 2 秒。我希望这有帮助。
element.focus({ preventScroll: true });
例如:
element.scrollIntoView({ behavior: "smooth" });
// also works for window.scrollTo({ top: y, behavior: 'smooth' })
element.focus({ preventScroll: true });
这样您就可以避免特定于浏览器的问题,并可以通过即时焦点进行更快的交互。
请参阅
https://github.com/w3c/csswg-drafts/issues/3744#issuecomment-685683932elm.on('scroll', function() {
if(elm[0].scrollTop === elm[0].scrollHeight)
$('#comment-input').focus();
});
对我不起作用;我在 body 上使用 overflow: hidden
,似乎在某些移动浏览器上将
scrollY
和 scrollX
设置为 0
;
就我而言,我需要从 top
和 left
滚动,所以我的代码解释了这一点;这是我的第一次尝试: let vh = window.innerHeight;
let vw = window.innerWidth;
let e = component.element;
let left = e.offsetLeft - vw / 2 + e.width / 2;
let top = e.offsetTop - vh / 2 + e.height / 2;
window.scrollTo({
left: left,
top: top,
behavior: "smooth",
});
return await new Promise((resolve, reject) => {
let failed = setTimeout(() => {
console.log({ scrollX: window.scrollX, scrollY: window.scrollY });
reject();
}, 2000);
let scrollHandler = () => {
if (abs(window.scrollY - top) < 1 && abs(window.scrollX - left) < 1) {
window.removeEventListener("scroll", scrollHandler);
clearTimeout(failed);
resolve();
}
};
window.addEventListener("scroll", scrollHandler);
scrollHandler();
});
我从这个答案
中发现了
"scrollend"
事件,但它不适用于所有移动浏览器;
这是我的代码,有时可以工作,但不适用于所有移动浏览器:
let e = component.element;
let vh = window.innerHeight;
let vw = window.innerWidth;
let left = e.offsetLeft - vw / 2 + e.width / 2;
let top = e.offsetTop - vh / 2 + e.height / 2;
window.scrollTo({
left,
top,
behavior: "smooth",
});
return await new Promise((resolve, reject) => {
let failed = setTimeout(() => {
console.log("scroll finish failed");
reject();
}, 2000);
window.addEventListener("scrollend", scroll_handler);
function scroll_handler() {
clearTimeout(failed);
window.removeEventListener("scrollend", scroll_handler);
resolve();
}
});
我计划采取解决方法:
通知用户使用支持的浏览器/操作系统;或使用
setTimeout
休眠指定的毫秒数,用户可以在应用程序的设置中配置该时间;滚动的最大时间是非常可预测的:用户在地图上移动角色,因此最多滚动一些像素