SVG '<path>' javascript 动画未按预期工作



我所取得的成就:

// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.setAttribute("cx", endPoint.x);
circle.setAttribute("cy", endPoint.y);
}
body {
height: 2000px;
background: #f1f1f1;
}
#circle {
fill: red;
}
#mySVG {
position: absolute;
top: 15%;
width: 100%;
height: 1000px;

}
.st1 {
fill: none;
stroke-dashoffset: 3px;
stroke: grey;
stroke-width: 4;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<path  class="st1" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<circle id="circle" cx="10" cy="10" r="10"/>
<path id="myline" class="st0" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>

我想要的是无论SVG<path>是哪种尺寸形状生长线应位于屏幕中间。

我尝试更改myline.style.strokeDashoffset = length //+newvalue - draw;的值,但所做的只是破坏一致性。 那么有没有人可以帮助我解决这个问题。?

任何帮助都将是非常值得赞赏的。

(更新/新答案(

我认为这正是你想要的...

更新(根据 OP 需要添加缓动(:

通过在#body上增加transition: stroke-dashoffset ease-in-out 0.2s;,使#body的长度变得平滑

#head的移动不能通过在#head上添加transition: cx ease-in-out 0.2s, cy ease-in-out 0.2s;来平滑,因为这会使它跳跃而不是沿着路径移动。

如果你想让#head沿着轨道平稳移动,你必须通过手动实现js的缓动来完成,这是很多工作。所以跳过了那部分。

let roadmapSvg = document.getElementById("roadmap-svg");
let track = document.getElementById("track");
let body = document.getElementById("body");
let head = document.getElementById("head");
let totalLength = track.getTotalLength();
let trackPoints = [];
let getTrackBounds = () => track.getBoundingClientRect();
let scaleFactor;
body.style.strokeDasharray = totalLength;
body.style.strokeDashoffset = totalLength;
function setScaleFactor(){
scaleFactor = roadmapSvg.getBoundingClientRect().width / roadmapSvg.viewBox.baseVal.width;
}   
setScaleFactor();
function setTrackPoints(){
let divisions = 1000;
let unitLength = totalLength / divisions;
trackPoints = [];
for(let i=0; i < divisions; i++){
let length = unitLength * i;
let {x,y} = track.getPointAtLength(length);
trackPoints.push({x: x*scaleFactor, y: y*scaleFactor, length});
}
}
setTrackPoints();
function draw(){
let currentLength = getCurrentLength();
body.style.strokeDashoffset = totalLength - currentLength;
headPos = track.getPointAtLength(currentLength);
head.setAttribute("cx", headPos.x);
head.setAttribute("cy", headPos.y);
}
function getCurrentLength(){
let centreY = window.innerHeight / 2;
let trackBounds = getTrackBounds();
let currentY = centreY - trackBounds.y;
if(currentY < 0) return 0;

// if currentY is greater that track height, that means the user has scrolled pass the track (and the whole svg) in such case the animation should be completed i.e. the head should be at the final position i.e. at totalLength 
if(currentY > trackBounds.height) return totalLength;

for(let point of trackPoints){
if(point.y >= currentY){
return point.length;
}
}

// (For safety) Sometimes none of the conditions match bcoz of low precision... Such situation only occurs a point very close to total length... Thus...
return totalLength;
}
document.addEventListener("scroll", draw);
window.addEventListener("resize", () => {
setScaleFactor();
setTrackPoints();
draw();
});
body {
background: #f1f1f1;
margin: 0;
padding: 0 20%;
font-family: sans-serif;
}
#roadmap-svg{
display: block;
max-width: 600px;
margin: 20px auto;
overflow: visible;
}
#roadmap-svg #head{
fill: red;
}
#roadmap-svg #track{
fill: none;
stroke-dashoffset: 3px;
stroke: grey;
stroke-width: 4;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
#roadmap-svg #body{
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
transition: stroke-dashoffset ease-in-out 0.2s;
}
.center-line{
position: fixed;
left: 0;
right: 0;
top: 50%;
border-top: 1px solid red;
background-color: rgba(255,255,255,0.9);
}
<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempora in eaque rem eligendi corrupti voluptate, maxime cum cumque, eius delectus minus neque, dolorem optio cupiditate ratione! Excepturi fugit culpa quo?
Cum optio error ex voluptatem rerum eius sunt, nemo necessitatibus, exercitationem voluptatum illum, rem quibusdam accusamus deserunt sed. Iste odio obcaecati enim voluptate temporibus ab illo maxime et sit minima.
Odio ut dignissimos sed dicta recusandae esse, at molestiae quibusdam, consequatur aspernatur facilis, perferendis voluptatum adipisci. Dolores molestiae quos, doloribus excepturi officiis laborum ex officia reprehenderit esse perspiciatis alias itaque.
Delectus illum, asperiores at a ab quibusdam corporis necessitatibus. Libero eos vero blanditiis modi cum rem maxime delectus quisquam, facilis saepe sed eius corrupti nobis sunt, unde obcaecati commodi velit.
Saepe adipisci consectetur blanditiis quos enim praesentium, at magnam quibusdam nisi! Dolore, esse beatae! Enim, quam cum, qui voluptates fugiat, nihil mollitia possimus doloremque porro aspernatur nesciunt velit. Cum, adipisci?
Dolores doloribus nihil delectus consequuntur id assumenda tempora, illum, earum ab quasi quaerat sequi et hic veniam excepturi eligendi quod perspiciatis voluptatem ratione reprehenderit! Corrupti minima facilis soluta adipisci animi!
Iure, sed exercitationem. Quidem assumenda omnis dicta ducimus sunt, quibusdam excepturi molestias cumque! Illum ipsum perferendis dicta optio eum consequuntur soluta, corrupti nostrum est sed quaerat voluptates dolores perspiciatis? Ex!
Consequatur corporis ratione beatae. Magni amet doloribus deserunt, accusamus suscipit earum accusantium perferendis adipisci inventore, ab commodi odio necessitatibus aut omnis. Et quisquam esse deleniti, reprehenderit nihil optio aperiam fugit.
Aliquid error voluptatibus, quis quo eveniet nulla corrupti veniam culpa voluptas possimus tenetur nisi recusandae quae modi, animi dolores. Provident saepe nobis quos tenetur, veritatis laborum cupiditate molestias fugit consectetur.
A, perspiciatis illo sequi non eos facere temporibus dignissimos blanditiis ipsum harum eius culpa adipisci est ab nobis saepe mollitia quis laboriosam tenetur, repellat molestias. Quos ipsa magni dolores rerum.</div>
<svg id="roadmap-svg" viewBox="0 0 760 300">
<path  id="track" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" />
<path id="body" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" />
<circle id="head" cx="10" cy="10" r="10"/>
</svg>
<div class="center-line">Center Line</div>
<div>Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente officia saepe facilis? Cupiditate rem vel, quaerat ratione ipsam magnam fugiat praesentium incidunt! Eveniet cum officia impedit obcaecati id animi rerum?
Non beatae inventore quos optio temporibus ratione doloremque ullam animi dolore reiciendis sint, esse consequatur asperiores assumenda repudiandae obcaecati ab quas molestias harum eveniet amet natus ea? Ipsum, dolore suscipit.
Explicabo assumenda minus, reprehenderit modi, laboriosam placeat saepe at repudiandae perferendis fugit asperiores itaque. Vero fugiat voluptas asperiores dolores dolorum quis ipsa sapiente deleniti odio, deserunt, iure voluptates. Error, tempore.
Doloribus nesciunt praesentium ad aut minus aliquam aspernatur quas qui incidunt sunt, maxime tempora facilis, cum assumenda. Dolorum a tempore itaque impedit, ad, corporis tenetur enim nulla quas, harum fuga!
Quae repellat, obcaecati voluptate inventore quidem, labore quo corporis repudiandae, vel doloremque perferendis numquam aliquam nisi? Vel architecto ullam fugiat error corrupti? Cumque amet illo, possimus assumenda eos unde deleniti.
Enim tenetur possimus a neque, voluptatum reprehenderit, cum magni blanditiis quam atque dolorum veniam eveniet repellendus. Modi quibusdam maxime corrupti harum! Ullam vitae assumenda laboriosam nam officia eaque. Totam, dolorem.
Ad sapiente itaque blanditiis, sint iusto nemo laborum corrupti cupiditate obcaecati quam ipsa quis perferendis vitae enim atque ex a ratione. Doloribus aspernatur id ipsa recusandae labore aliquid, totam aperiam?
Recusandae delectus quidem, aspernatur nulla expedita accusantium quod praesentium inventore qui, pariatur ullam maxime! Numquam, sed sequi rem voluptates asperiores qui, culpa nesciunt magnam, quas doloribus praesentium et adipisci tempora.
Veniam, placeat vel nesciunt recusandae voluptates laboriosam totam doloremque saepe. Nam quo similique vero esse possimus architecto officiis harum ratione perspiciatis dolor ut, molestias odit consequatur quam asperiores? Id, quasi!
Ex expedita impedit aliquam et commodi voluptatibus, consequatur voluptate ea explicabo deserunt. Sapiente quo consequuntur enim dolores ea officia. Inventore ipsa dignissimos iste qui magnam reiciendis eveniet optio laudantium fugiat!</div>

我尝试了几种方法来移动点

  • 盒子里面
  • 直到最后才滑落
  • 对原始内容进行最少的更改。

我进行了以下更改(下面的工作示例(:

  • 我放置 2000 的百分比的除法器(等于 2000px,包含的身体高度(
  • 我将顶部的滚动量乘以 18(无论顶部还是底部变得行为不正常,合适的值都是一种折衷(
  • 最后我检查了百分比值不大于 1(它开始吃另一端的"蠕虫"(。

就是这样!也许不是最华丽的,但有效。

这里的问题是 svg 线从上到下不是线性的,因此无法选择直接元素相关值,或者我至少没有找到一些。因此,我最终得到了简单的解决方案并玩弄了参数。

// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.documentElement.scrollTop * 18) / 2000;
if (scrollpercent > 1) scrollpercent = 1;
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.setAttribute("cx", endPoint.x);
circle.setAttribute("cy", endPoint.y);
}
body {
height: 2000px;
background: #f1f1f1;
}
#circle {
fill: red;
}
#mySVG {
position: absolute;
top: 15%;
width: 100%;
height: 1000px;

}
.st1 {
fill: none;
stroke-dashoffset: 3px;
stroke: grey;
stroke-width: 4;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<path  class="st1" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<circle id="circle" cx="10" cy="10" r="10"/>
<path id="myline" class="st0" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>

根据评论和对 css 和 svg 代码的一些修改,我能够将行移到页面中心,请查看下面的工作示例:

// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.setAttribute("cx", endPoint.x);
circle.setAttribute("cy", endPoint.y);
}
body {
margin: 0;
height: 1000px;
background: #f1f1f1;
}
#circle {
fill: red;
}
#mySVG {
top: 15%;
position: absolute;
width: 100%;
}
.st1 {
fill: none;
stroke-dashoffset: 1;
stroke: grey;
stroke-width: .5;
stroke-miterlimit: 1;
stroke-dasharray: 2;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 1;
stroke-miterlimit: 1;
stroke-dasharray: 2;
}
.grid {
position: fixed;
width: 1px;
height: 100%;
background: blue;
left: 50%;
top: 0;
}
<div class="grid"></div>
<svg id="mySVG" viewBox="0 0 200 72" preserveAspectRatio="xMidYMin slice">
<path class="st1" stroke-dasharray="10,9" d="m 0,5 0,4 c 0,3 2,6 5,6 l 108,0 c 4,0 7,4 7,8 0,4 -3,7 -7,7 l -25,0 c -3,0 -6,3 -6,6 0,3 3,6 6,6 l 35,0 c 4,0 7,4 7,8 0,4 -3,7 -7,7 l -18,0 c -3,0 -5,3 -5,6 l 0,4" />
</svg>
<svg id="mySVG" viewBox="0 0 200 72" preserveAspectRatio="xMidYMin slice">
<circle id="circle" cx="0" cy="3" r="2" />
<path id="myline" class="st0" stroke-dasharray="10,9" d="m 0,5 0,4 c 0,3 2,6 5,6 l 108,0 c 4,0 7,4 7,8 0,4 -3,7 -7,7 l -25,0 c -3,0 -6,3 -6,6 0,3 3,6 6,6 l 35,0 c 4,0 7,4 7,8 0,4 -3,7 -7,7 l -18,0 c -3,0 -5,3 -5,6 l 0,4" />
</svg>

我会选择不同的方法。我知道您想在最接近屏幕中间的路径上找到点。让我们这样做:

  1. 找到屏幕中间的坐标并将其转换为路径的坐标系。SVG API有两个函数:.getScreenCTM()SVGPoint.matrixTransform()
  2. 查找路径上最接近这些坐标的点(及其沿路径的距离(。这涉及到一些数学和搜索算法。Mike Bostock展示了这样的算法,它在这里使用。请注意,他的函数可以进行一些调整(precision参数(。
  3. 使用这些数据绘制圆圈和虚线偏移量。

通过为滚动事件(第二个变体(引入限制来优化这一点,然后设置 CSS 过渡以避免可见的跳转,这可能是一个好主意。

圆圈定位的过渡仅适用于 CSS 转换属性。(我的解决方案不一定在过渡时沿路径移动圆圈。这是可以实现的,但远远超出了这个答案的范围。

var mySVG = document.getElementById("mySVG");
// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var pathLength = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = pathLength;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = pathLength;
// throttled scroll event listener
function throttle(ms, callback) {
var timer, lastCall=0;
return function() {
var now = new Date().getTime(),
diff = now - lastCall;
if (diff >= ms) {
lastCall = now;
callback();
}
};
}
window.addEventListener("scroll", throttle(100, myFunction));
// one initial execution
myFunction();
function myFunction() {
var center = mySVG.createSVGPoint();
// middle of browser viewport
center.x = window.innerWidth / 2;
center.y = window.innerHeight / 2;
// transform to path coordinate system
var matrix = myline.getScreenCTM().inverse();
center = center.matrixTransform(matrix);
//find nearest length on path
var draw = getNearestLength(center);
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = -draw - pathLength;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.style.transform = "translate(" + endPoint.x + "px, " + endPoint.y + "px)";
}
function getNearestLength(point) {
var precision = 8,
best,
bestLength,
bestDistance = Infinity;
// linear scan for coarse approximation
for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
if ((scanDistance = distance2(scan = myline.getPointAtLength(scanLength))) < bestDistance) {
best = scan, bestLength = scanLength, bestDistance = scanDistance;
}
}
// binary search for precise estimate
precision /= 2;
while (precision > 0.5) {
var before,
after,
beforeLength,
afterLength,
beforeDistance,
afterDistance;
if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = myline.getPointAtLength(beforeLength))) < bestDistance) {
best = before, bestLength = beforeLength, bestDistance = beforeDistance;
} else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = myline.getPointAtLength(afterLength))) < bestDistance) {
best = after, bestLength = afterLength, bestDistance = afterDistance;
} else {
precision /= 2;
}
}
return bestLength;
function distance2(p) {
var dx = p.x - point.x,
dy = p.y - point.y;
return dx * dx + dy * dy;
}
}
body {
height: 2000px;
background: #f1f1f1;
}
#circle {
fill: red;
}
#mySVG {
position: absolute;
top: 15%;
width: 100%;
height: 1000px;

}
.st1 {
fill: none;
stroke-dashoffset: 3px;
stroke: grey;
stroke-width: 4;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: red;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
transition: stroke-dashoffset 0.2s;
}
#circle {
transform: translate(10px, 10px);
transition: transform 0.2s;
}
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<path  class="st1" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>
<svg id="mySVG" viewBox="0 0 60 55" preserveAspectRatio="xMidYMin slice" style="width: 6%; padding-bottom: 42%; height: 1px; overflow: visible">
<circle id="circle" cx="0" cy="0" r="10"/>
<path id="myline" class="st0" stroke-dasharray="10,9" d="M 20 0 v 20 a 30 30 0 0 0 30 30 h 600 a 40 40 0 0 1 0 80 h -140 a 30 30 0 0 0 0 60 h 200 a 40 40 0 0 1 0 80 h -100 a 30 30 0 0 0 -30 30 v 20" /> Sorry, your browser does not support inline SVG.
</svg>

最新更新