我希望能够平滑地滚动到我页面的某个部分。在jQuery中这样做:
$('html, body').animate({
scrollTop: $('#anchorOfMyTargetSection').offset().top
}, 'slow');
在Dart,我可以制作卷轴,但我不知道如何为它制作动画:
int targetSection = querySelector('#anchorOfMyTargetSection').offsetTop;
window.scrollTo( 0, targetSection );
更具体地说,我做了一个单页,其中包含指向自我页面中各个部分的链接。
以下是链接:
<nav>
<ul class="nav-list">
<li><a href="http://example.com/#title">Home</a></li>
<li><a href="http://example.com/#about">About us</a></li>
<li><a href="http://example.com/#products">Products</a></li>
<li><a href="http://example.com/#services">Services</a></li>
<li><a href="http://example.com/#contact">Contact</a></li>
</ul>
</nav>
以下是完整的jQuery代码:
// Generated by LiveScript 1.2.0
var smoothScrolling;
smoothScrolling = function(selector, offset){
return $(selector).click(function(click){
var link, anchor;
click.preventDefault();
link = $(this).attr('href');
anchor = link.substring(link.indexOf('#'));
return $('html ,body').animate({
scrollTop: $(anchor).offset().top + offset
}, 'slow');
});
};
smoothScrolling('.nav-list a', -45);
这里是Dart代码(当然没有动画部分):
import 'dart:html';
void main() {
smoothScrollingOffset = -45;
querySelectorAll('.nav-list a').onClick.listen(smoothScrolling);
}
int smoothScrollingOffset = 0;
void smoothScrolling(MouseEvent click) {
click.preventDefault();
String link = click.target.toString();
String anchor = link.substring( link.indexOf('#') );
int targetPosition = querySelector('$anchor').offsetTop;
window.scrollTo( 0 , targetPosition + smoothScrollingOffset );
}
请记住,我不是Dart的新手,而是一般的编程新手。如果您看到Dart代码中可以改进的东西,请告诉它。
答案 0 :(得分:7)
更新:仅使用dart:html库。
这些是链接
<nav>
<ul class="nav-list">
<li><a href="http://example.com/#title">Home</a></li>
<li><a href="http://example.com/#about">About us</a></li>
<li><a href="http://example.com/#products">Products</a></li>
<li><a href="http://example.com/#services">Services</a></li>
<li><a href="http://example.com/#contact">Contact</a></li>
</ul>
</nav>
在您的Dart代码中导入dart:html库以访问DOM。
import 'dart:html';
这是函数,这就是它的作用:
void smoothScrolling(String selector, {int offset: 0, int duration: 500}) {
// The detection of the clicks and the selectors of the links are specified at the bottom of the function
// Let's suppose you click a link to visit 'http://example.com/#contact'
// When you click the link
void trigger(MouseEvent click) {
// Prevent to visit the resources, like normally does
click.preventDefault();
// Get the resource link of the clicked element. In this case: 'http://example.com/#contact'
String link = click.target.toString();
// Extract the anchor. In this case from 'http://example.com/#contact' will extract '#contact'
String anchor = link.substring( link.indexOf('#') );
// With the extracted anchor, search the corresponding element and get his position.
// In this case gets the position of the element with 'contact' in the 'id' attribute
int targetPosition = querySelector('$anchor').offsetTop;
// Before to translate to the element,
// you can specify if you want to translate some distance before or after the element
targetPosition += offset;
// Let's move in direction to the section
// We know than in Dart there are 60 frames per second, that means a frame duration is
// 1000 milliseconds divided in 60 frames = per frame is 16.66 milliseconds long.
// But 16.66 milliseconds multiplied by 60 = 999.99 milliseconds. That is ALMOST a second.
// And that means than there will be 59.99 frames in 999.99 milliseconds
// But we cannot handle frame fractions, we should round frames to integers
// So 59.99 frames will be rounded to 60 frames
// Calculate the total number of frames
int totalFrames = ( duration / (1000 / 60) ).round();
// The first animation frame will be the number 1, the number 0 will be the start point
int currentFrame = 0;
// In this case the start point will be the current position
int currentPosition = window.scrollY;
// The end point will be the target position, we should know how many distance there is between the start and end point.
// The positive and negative numbers represents the same distance, that means than 'y' and '-y' are the same.
// Example: 10 and -10 are the same distance.
// Calculate the distance between the start and end point.
int distanceBetween = targetPosition - currentPosition;
// Then calculate how many distance should move per frame
num distancePerFrame = distanceBetween / totalFrames;
// The animation function is triggered by first time more later in the code
// And when is triggered
void animation(num frame) {
// First we look the number of the frame we are going to run.
// When all the frames are complete the animation function will not be executed again
if ( totalFrames >= currentFrame ) {
// In every frame we are going to move some distance with direction to the target.
// The direction (in this case will be only up or down) depends of the 'distanceBetween' number.
// Let's explore this part with an example: You are 10 pixels from your target, your target is at the point 20,
// Remember, to calculate the distance between you and the target we do 'targetPosition - currentPosition'.
// If you are 10 pixels on from the target (at the point 10) the result will be: 20 - 10 = 10.
// If you are 10 pixels down from the target (at the point 30) the result will be: 20 - 30 = -10.
// You see how the number is the same but with different polarity?. And 10 and - 10 represent the same distance
// The direction depends of the number 0, if you move closer to the 0 you will go down, if you move away the 0 you will move up.
// Let's move 5 pixels:
// 10 + 5 = 15. You will move down, because you will be more away of the 0, your target is at the number 20.
// -10 + 5 = -5. You will move up, because you will be more closer to the 0, your target is at the number 0.
// Let's move to the point where we should be in this frame
window.scrollTo( 0, currentPosition );
// Calculate the point where we should be in the next frame
currentPosition += distancePerFrame;
// We get ready to execute the next frame
currentFrame++;
// When the time of this frame (16.66 milliseconds) is complete immediately starts the next frame.
window.animationFrame.then(animation);
}
}
// Here is triggered the animation by first time
window.animationFrame.then(animation);
}
// Here are the links' selectors and the detection of the clicks
querySelectorAll(selector).onClick.listen(trigger);
}
要使用该功能,我们应指定链接的选择器,以及可选的滚动偏移和/或持续时间。
void main() {
// To use the function we should specify the links' selectors,
// and optionally an offset and/or the duration of the scrolling,
smoothScrolling('.nav-list a',
offset: -45,
duration: 2500);
}
我将解释要点,所有细节都在代码中进行了评论。
标记的其他事情是我长期使用Blender,开源3D动画软件,感谢Blender进入编程世界。所以我知道我在谈论动画。
这是第一个难点。如果你在Dart docs中搜索动画来处理动画,你会发现animationFrame,这似乎没问题。
但首先,什么是框架?也许你听说过每秒24帧的东西。这意味着,在这种情况下,帧为1秒除以24 =每帧为0.0416秒长=每41.66毫秒一帧。
然后让我们处理帧作为时间块,这个时间块通常作为整数处理。我们通常会这样说:这个屏幕是每秒24帧,但是这个屏幕不是每秒24.6帧。
但是文档中存在问题。
Future完成了一个表示浮点的时间戳 自页面以来经过的毫秒数的值 开始加载(这也是此次调用的时间戳 animationFrame)。
好的,这意味着我可以知道页面中的用户已经有多少时间了,但是没有任何关于我每秒可以知道多少次的参考,这就是我在谈话时所希望知道的关于帧:一秒钟内有多少帧?
经过一些实验,我发现在Dart中每秒有60帧
1000毫秒除以60帧=每帧是16.66 毫秒长。但是16.66毫秒乘以60 = 999.99 毫秒。这几乎是一秒钟。这意味着比那样 在999.99毫秒内是59.99帧但是我们无法处理帧 分数,我们应该将帧舍入为整数。所以59.99帧 四舍五入到60帧
记住一帧长16.66毫秒。
// Set the duration of your animation in milliseconds
int duration = 1000;
// Calculate the total number of frames
int totalFrames = ( duration / (1000 / 60) ).round();
// The first animation frame will be the number 1, the number 0 will be the start point
int currentFrame = 0;
// The animation function is triggered by first time more later in the code
// And when is triggered
void animation(num frame) {
// First we look the number of the frame we are going to run.
// When all the frames are complete the animation function will not be executed again
if ( totalFrames >= currentFrame ) {
// ===========================================
// Here what we are going to do in every frame
// ===========================================
// We get ready to execute the next frame
currentFrame++;
// When the time of this frame (16.66 milliseconds) is complete immediately starts the next frame.
window.animationFrame.then(animation);
}
}
// Here is triggered the animation by first time
window.animationFrame.then(animation);
注意:强>
1。我发现编写smoothScrolling函数的错误,但不要担心,只影响Dart虚拟机,编译的JavaScript按预期工作。我试图找出究竟是什么导致了这个bug,所以可以报告它。
我发现的是在动画函数make中使用数字distancePerFrame而不是函数不循环。
2。从技术上讲,这不是'平滑滚动',是'线性滚动'。
答案 1 :(得分:3)
您可以使用包animation来完成工作
这里有一个使用示例:
import 'dart:html';
import 'package:animation/animation.dart');
main() {
var el = query('#box');
var properties = {
'left': 1000,
'top': 350
};
animate(el, properties: properties, duration: 5000);
}
答案 2 :(得分:2)
更新:带有计时和辅助功能的最终版本,用于规范滚动动画的持续时间。
应在所有目标浏览器中进行测试
import 'dart:html';
import 'dart:async';
import 'dart:math';
void main() {
Element a = querySelector("#a"),
b = querySelector("#b"),
c = querySelector("#c");
document.onClick.first
.then((_) => scrollTo(c, getDuration(c, 2), TimingFunctions.easeInOut))
.then((_) => scrollTo(a, getDuration(a, 2), TimingFunctions.easeOut))
.then((_) => scrollTo(c, getDuration(c, 5), TimingFunctions.easeOut))
.then((_) => scrollTo(b, getDuration(b, 2), TimingFunctions.easeOut))
.catchError(print);
}
double fullOffsetTop(Element element) =>
element.getBoundingClientRect().top +
window.pageYOffset -
document.documentElement.clientTop;
Duration getDuration(Element targetElement, num speed){
var distance = (window.pageYOffset - fullOffsetTop(targetElement)).abs();
return new Duration(milliseconds: distance ~/ speed);
}
Future scrollTo(Element el, Duration duration, TimingFunction tf) {
var isCompleted = false,
isInterrupted = false,
completer = new Completer(),
startPos = window.pageYOffset,
targetPos = fullOffsetTop(el),
overScroll =
max(targetPos + window.innerHeight - document.body.scrollHeight, 0),
startTime = null,
direction = (targetPos - startPos).sign;
targetPos -= overScroll;
var totalDistance = (targetPos - startPos).abs();
//make text unselectable and disable events
//like onMouseOver for better performance during the scroll.
String disable =
"-webkit-user-select: none;"
"-moz-user-select: none;"
"-ms-user-select: none;"
"-o-user-select: none;"
"user-select: none;"
"pointer-events: none;";
String oldBodyStyle = document.body.getAttribute("style") != null ?
document.body.getAttribute("style") : "";
//return control to the user if he/she tries to interact with the page.
window.onMouseWheel.first.then((_) => isInterrupted = isCompleted = true);
window.onKeyDown.first.then((_) => isInterrupted = isCompleted = true);
document.body.setAttribute("style", disable + oldBodyStyle);
iter() {
window.animationFrame.then((_) {
if (startTime == null) startTime = window.performance.now();
var deltaTime = window.performance.now() - startTime,
progress = deltaTime / duration.inMilliseconds,
precision = (1000 / 60 / duration.inMilliseconds) / 4,
dist = totalDistance * tf(progress, precision);
var curPos = startPos + dist * direction;
if (progress >= 1.0) isCompleted = true;
if (!isCompleted) {
window.scrollTo(0, curPos.toInt());
iter();
} else {
document.body.setAttribute("style", document.body.getAttribute("style"
).replaceFirst(disable, ""));
isInterrupted ? completer.completeError("Interrupted by the user") :
completer.complete("completed");
}
});
}
iter();
return completer.future;
}
typedef num TimingFunction(num time,num precision);
abstract class TimingFunctions{
static TimingFunction easeInOut = makeCubicBezier(0.42, 0, 0.58, 1);
static TimingFunction easeOut = makeCubicBezier(0.25, 0.1, 0.25, 1);
}
TimingFunction makeCubicBezier(x1, y1, x2, y2) {
var curveX = (t) {
var v = 1 - t;
return 3 * v * v * t * x1 + 3 * v * t * t * x2 + t * t * t;
};
var curveY = (t) {
var v = 1 - t;
return 3 * v * v * t * y1 + 3 * v * t * t * y2 + t * t * t;
};
var derivativeCurveX = (t) {
var v = 1 - t;
return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) *
x2;
};
return (t, precision) {
var x = t,t0,t1,t2,x2,d2,i;
for (i = 0; i < 8; i++) {
t2 = x;
x2 = curveX(t2) - x;
if (x2.abs() < precision) return curveY(t2);
d2 = derivativeCurveX(t2);
if (d2.abs() < 1e-6) break;
t2 = t2 - x2 / d2;
}
t0 = 0;
t1 = 1;
t2 = x;
if (t2 < t0) return curveY(t0);
if (t2 > t1) return curveY(t1);
while (t0 < t1) {
x2 = curveX(t2);
if ((x2 - x).abs() < precision) return curveY(t2);
if (x > x2) {
t0 = t2;
} else {
t1 = t2;
}
t2 = (t1 - t0) * .5 + t0;
}
return curveY(t2);
};
}
https://www.youtube.com/watch?v=IyHb0SJms6w - 解释pointer-events: none;
如何帮助。
https://www.youtube.com/watch?v=hAzhayTnhEI - 为什么你应该使用AnimationFrame而不是计时器。
如果您可以使用Web动画会更好,但是,我认为,滚动效果不可能,因为它不是css属性。
答案 3 :(得分:0)
要使滚动到特定ID的动画,您可以在此函数中传递它,或者只是将ID硬编码:
void moveTop() {
var targetSection = querySelector('#id-where-to-scroll').offsetTop;
window.scrollTo({'top': targetSection, 'left': 0, 'behavior': 'smooth'});
}