SVG 曲线上的动画选取框 [英] Animate marquee on SVG curve

查看:27
本文介绍了SVG 曲线上的动画选取框的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个环绕 SVG 圆圈的文本,该圆圈根据窗口大小进行缩放 – 感谢用户 enxaneta

解决方案

主要思想是路径必须盘绕两次.当 startOffset 为 50% 时,您将其设为 0.此外,由于在调整窗口大小时路径的长度会发生变化,因此您需要重新计算字体大小.希望能帮到你.

function Init() {让 w = wrap.clientWidth;让 h = wrap.clientHeight;ellipse.setAttributeNS(null, "viewBox", `0 0 ${w} ${h}`);让 d = `M${w/10},${h/2}A${4 * w/10},${4 * h/10} 0 0 0 ${9 *带/10} ${5 * h/10} A${4 * w/10},${4 * h/10} 0 0 0 ${w/10} ${5 *H/10} A${4 * w/10},${4 * h/10} 0 0 0 ${9 * w/10} ${5 * h/10} A${4 *带/10},${4 * h/10} 0 0 0 ${w/10} ${5 * h/10}`;thePath.setAttributeNS(null, "d", d);让paths_length = thePath.getTotalLength();tp.style.fontSize = 路径长度/205;}window.setTimeout(function() {在里面();window.addEventListener("resize", Init, false);}, 15);让所以 = 0;功能选框(){请求动画帧(选框);tp.setAttributeNS(null, "startOffset", so + "%");如果(所以> = 50){所以 = 0;}所以 += 0.05;}Marquee();

#wrap{width:100vw;高度:100vh}SVG {背景:#eee;}

<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000"><path id="thePath" fill="gold" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500 A400,400 0 0 0 900 500 A40010,000"/><text stroke="#000000" ><textPath xlink:href="#thePath" dy="5" id="tp">即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 •</textPath></svg>

更新

OP 正在评论:

<块引用>

此代码段似乎适用于用例,但是当我尝试将其应用于另一个字体系列时,尺寸关闭并且两个循环开始重叠

一个简单的解决方法是将属性 textLength 设置为等于路径长度除以 2(因为路径盘绕两次 - 是应该的两倍).您还需要使用 lengthAdjust="spacingAndGlyphs" 来控制文本如何拉伸或压缩到该长度.

function Init() {让 w = wrap.clientWidth;让 h = wrap.clientHeight;ellipse.setAttributeNS(null, "viewBox", `0 0 ${w} ${h}`);让 d = `M${w/10},${h/2}A${4 * w/10},${4 * h/10} 0 0 0 ${9 *带/10} ${5 * h/10} A${4 * w/10},${4 * h/10} 0 0 0 ${w/10} ${5 *H/10} A${4 * w/10},${4 * h/10} 0 0 0 ${9 * w/10} ${5 * h/10} A${4 *带/10},${4 * h/10} 0 0 0 ${w/10} ${5 * h/10}`;thePath.setAttributeNS(null, "d", d);让 path_length = thePath.getTotalLength();//////////////////////////////////////////////////tp.setAttributeNS(null,"textLength",path_length/2)//////////////////////////////////////////////////tp.style.fontSize = path_length/200;}window.setTimeout(function() {在里面();window.addEventListener("resize", Init, false);}, 15);让所以 = 0;功能选框(){请求动画帧(选框);tp.setAttributeNS(null, "startOffset", so + "%");如果(所以> = 50){所以 = 0;}所以 += 0.05;}Marquee();

#wrap{width:100vw;高度:100vh}SVG {背景:#eee;字体系列:控制台;}

<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000"><path id="thePath" fill="gold" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500 A400,400 0 0 0 900 500 A40010,000"/><text stroke="#000000" ><textPath xlink:href="#thePath" id="tp" lengthAdjust="spacingAndGlyphs">即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 • 即将推出 •即将推出•</textPath></svg>

如果文本变得过于拉伸/挤压,您可能还需要添加/删除一些即将推出•.

更新 2

显然最后一个解决方案不适用于 Firefox.这是该问题的另一种解决方案.

最初我将字体大小设置得比需要的大得多.接下来我检查文本长度是否大于路径长度的一半,如果是,我将减小字体大小.我在一个 while 循环中执行此操作:

function Init() {让 w = wrap.clientWidth;让 h = wrap.clientHeight;ellipse.setAttributeNS(null, "viewBox", `0 0 ${w} ${h}`);让 d = `M${w/10},${h/2}A${4 * w/10},${4 * h/10} 0 0 0 ${9 *带/10} ${5 * h/10} A${4 * w/10},${4 * h/10} 0 0 0 ${w/10} ${5 *H/10} A${4 * w/10},${4 * h/10} 0 0 0 ${9 * w/10} ${5 * h/10} A${4 *带/10},${4 * h/10} 0 0 0 ${w/10} ${5 * h/10}`;thePath.setAttributeNS(null, "d", d);让 path_length = thePath.getTotalLength();//从比需要更大的尺寸开始让 font_size = 100;ellipse.style.fontSize = font_size+"px";//而文本长度大于路径长度的一半while(tp.getComputedTextLength() > path_length/2 ){//减小字体大小font_size -=.25;//重置字体大小ellipse.style.fontSize = font_size+"px";}}window.setTimeout(function() {在里面();window.addEventListener("resize", Init, false);}, 15);让所以 = 0;功能选框(){请求动画帧(选框);tp.setAttributeNS(null, "startOffset", so + "%");如果(所以> = 50){所以 = 0;}所以 += 0.02;}Marquee();

html, body {边距:0;高度:100%;宽度:100%;}身体 {字体系列:Arimo",无衬线;}#裹{宽度:100%;高度:100%;位置:固定;顶部:0;左:0;}文本 {文本转换:大写;字体粗细:更轻;}

<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000"><path id="thePath" fill="transparent" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500"/><textstroke="黑色"><textPath xlink:href="#thePath" dy="5" id="tp" lengthAdjust="spacingAndGlyphs">即将推出•即将推出•即将推出•即将推出•即将推出•即将推出•即将推出•<;/textPath></svg>

I have a text that is wrapping around an SVG circle which is scaling depending on window size – thanks to user enxaneta https://stackoverflow.com/a/56036245/10727821. I want to animate the text so that it would be revolving around the center like a marquee. For this, my code currently looks like this:

function Init(){
        let wrap = document.getElementById('wrap');
        let thePath = document.getElementById('thePath');
        let ellipse = document.getElementById('ellipse');
        let w = wrap.clientWidth;
        let h = wrap.clientHeight;
        ellipse.setAttributeNS(null,"viewBox",`0 0 ${w}  ${h}`);
        let d = `M${w/10},${h/2}A${4*w/10},${4*h/10} 0 0 0 ${9*w/10} ${5*h/10} A${4*w/10},${4*h/10} 0 0 0 ${w/10} ${5*h/10}`

    thePath.setAttributeNS(null,"d", d)
    }

    window.setTimeout(function() {
      Init();
      window.addEventListener('resize', Init, false);
    }, 15);

    let so = 0

    function Marquee(){
        let tp = document.getElementById('tp');
          requestAnimationFrame(Marquee)
          tp.setAttributeNS(null,"startOffset",so+"%");
          if(so >= 50){so = 0;}
          so+= .05
        }

    Marquee()

<div id="wrap">
    <svg id="ellipse" version="1.1" viewBox="0 0 1000 1000" preserveAspectRatio="none">
    <path id="thePath" fill="transparent" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500"  />


       <text stroke="black" font-size="20">
          <textPath xlink:href="#thePath" dy="5" id="tp">
                Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
                Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
                Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
          </textPath>
        </text>
    </svg>
</div>

This is working well, except the text gets "swallowed" at the end of the curve (see attached image). I'd like to have it make a full rotation without any interruption. I have tried changing the so variable to a negative value, but this ends up in the text being too far offset so it would only slowly creep onto the page. I was thinking to prepend a text fragment after a certain time, but this wouldn't take into account the startOffset movement and would probably not work…

Thankful for any hints, also those using JS libraries or plugins!

解决方案

The main idea is that the path has to coil twice. And when the startOffset is at 50% you make it 0. Also because the length of the path is changing when you resize the window you need to recalculate the font-size. I hope it helps.

function Init() {
  let w = wrap.clientWidth;
  let h = wrap.clientHeight;
  ellipse.setAttributeNS(null, "viewBox", `0 0 ${w}  ${h}`);
  let d = `M${w / 10},${h / 2}A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 *
    w /
    10} ${5 * h / 10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${w / 10} ${5 *
    h /
    10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 * w / 10} ${5 * h / 10} A${4 *
    w /
    10},${4 * h / 10} 0 0 0 ${w / 10} ${5 * h / 10}`;

  thePath.setAttributeNS(null, "d", d);

  let paths_length = thePath.getTotalLength();
  tp.style.fontSize = paths_length / 205;
}

window.setTimeout(function() {
  Init();
  window.addEventListener("resize", Init, false);
}, 15);

let so = 0;

function Marquee() {
  requestAnimationFrame(Marquee);
  tp.setAttributeNS(null, "startOffset", so + "%");
  if (so >= 50) {
    so = 0;
  }
  so += 0.05;
}

Marquee();

#wrap{width:100vw; height:100vh}
  svg {
    background:#eee;
  }

<div id="wrap">
<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000">  
<path id="thePath" fill="gold" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500 A400,400 0 0 0 900 500 A400,400 0 0 0 100 500"  />
  
  
   <text stroke="#000000" >
      <textPath xlink:href="#thePath" dy="5" id="tp">
            Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
      </textPath>
    </text>
</svg>
</div>

UPDATE

The OP is commenting:

This snippet seems to be working fine for the use case, but when I try to apply it to another font family, the dimensions are off and the two loops start overlapping

One easy fix would be setting the attribute textLength equal to the length of the path divided by 2 (since the path is coiling twice - is twice as long as it should be). Also you need tu use lengthAdjust="spacingAndGlyphs" that is controlling how the text is stretched or compressed into that length.

function Init() {
  let w = wrap.clientWidth;
  let h = wrap.clientHeight;
  ellipse.setAttributeNS(null, "viewBox", `0 0 ${w}  ${h}`);
  let d = `M${w / 10},${h / 2}A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 *
    w /
    10} ${5 * h / 10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${w / 10} ${5 *
    h /
    10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 * w / 10} ${5 * h / 10} A${4 *
    w /
    10},${4 * h / 10} 0 0 0 ${w / 10} ${5 * h / 10}`;

  thePath.setAttributeNS(null, "d", d);
  let path_length =  thePath.getTotalLength();
  
  //////////////////////////////////////////////////
  tp.setAttributeNS(null,"textLength",path_length/2)
  //////////////////////////////////////////////////
  
  tp.style.fontSize = path_length / 200;
}

window.setTimeout(function() {
  Init();
  window.addEventListener("resize", Init, false);
}, 15);

let so = 0;

function Marquee() {
  requestAnimationFrame(Marquee);
  tp.setAttributeNS(null, "startOffset", so + "%");
  if (so >= 50) {
    so = 0;
  }
  so += 0.05;
}

Marquee();

#wrap{width:100vw; height:100vh}
  svg {
    background:#eee;
    font-family:consolas;
  }

<div id="wrap">
<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000">  
<path id="thePath" fill="gold" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500 A400,400 0 0 0 900 500 A400,400 0 0 0 100 500"  />
  
  
   <text stroke="#000000" >
      <textPath xlink:href="#thePath" id="tp"  lengthAdjust="spacingAndGlyphs">Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • </textPath>
    </text>
</svg>
</div>

You may also need to add/remove some Coming Soon • if the text becomes too stretched / squished.

UPDATE 2

Apparently the last solution do not work on Firefox. This is another solution to this problem.

Initially I'm setting the font size much bigger than needed. Next I check if the text length is bigger than half path length, and if so I'm reducing the font size. I'm doing this in a while loop:

function Init() {
  let w = wrap.clientWidth;
  let h = wrap.clientHeight;
  ellipse.setAttributeNS(null, "viewBox", `0 0 ${w}  ${h}`);
  let d = `M${w / 10},${h / 2}A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 *
    w /
    10} ${5 * h / 10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${w / 10} ${5 *
    h /
    10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 * w / 10} ${5 * h / 10} A${4 *
    w /
    10},${4 * h / 10} 0 0 0 ${w / 10} ${5 * h / 10}`;

  thePath.setAttributeNS(null, "d", d);
  let path_length =  thePath.getTotalLength();
  
  
  //begin at a bigger size than needed
  let font_size = 100;
  ellipse.style.fontSize = font_size+"px"; 
  
  // while the text length is bigger than half path length 
  while(tp.getComputedTextLength() > path_length / 2 ){
    //reduce the font size
    font_size -=.25;
    //reset the font size 
    ellipse.style.fontSize = font_size+"px";
  }
}



window.setTimeout(function() {
  Init();
  window.addEventListener("resize", Init, false);
}, 15);

let so = 0;

function Marquee() {
  requestAnimationFrame(Marquee);
  tp.setAttributeNS(null, "startOffset", so + "%");
  if (so >= 50) {
    so = 0;
  }
  so += 0.02;
}

Marquee();

html, body {
    margin: 0;
    height: 100%;
    width: 100%;
}

body {
    font-family: "Arimo", sans-serif;
}

#wrap{
    width:100%;
    height:100%;
    position: fixed;
    top: 0;
    left: 0;  
}
text {
    text-transform: uppercase;
    font-weight: lighter;
}

<div id="wrap">
    <svg id="ellipse" version="1.1" viewBox="0 0 1000 1000">
    <path id="thePath" fill="transparent" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500"  />


       <text stroke="black">
          <textPath xlink:href="#thePath" dy="5" id="tp" lengthAdjust="spacingAndGlyphs">Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •</textPath>
        </text>
    </svg>
</div>

这篇关于SVG 曲线上的动画选取框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆