SVG曲线上的动画字幕

时间:2019-05-08 13:20:53

标签: javascript html css svg

我有一个环绕SVG圆的文本,该文本根据窗口大小而缩放-感谢用户enxaneta https://stackoverflow.com/a/56036245/10727821。我想对文本进行动画处理,以使其像选取框一样围绕中心旋转。为此,我的代码当前如下所示:

<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>

<script>

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()

</script>

这很好用,除了文本在曲线的末端“被吞咽”(参见附图)。我希望它能完全旋转而不中断。我曾尝试将so变量更改为负值,但这最终导致文本偏移太远,因此它只会缓慢地爬到页面上。我本来想在一段时间后添加一个文本片段,但这并没有考虑到startOffset的动作,因此可能无法正常工作……

感谢任何提示,也感谢那些使用JS库或插件的提示!

enter image description here

1 个答案:

答案 0 :(得分:1)

主要思想是路径必须绕圈两次。当startOffset为50%时,将其设置为0。另外,由于在调整窗口大小时路径的长度在变化,因此需要重新计算字体大小。希望对您有所帮助。

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>

更新

OP正在评论:

  

此代码段似乎在用例上运行良好,但是当我尝试将其应用于另一个字体系列时,尺寸已关闭,两个循环开始重叠

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

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>

如果文本太拉伸/压扁,您可能还需要添加/删除一些Coming Soon •

更新2

显然,最后一个解决方案在Firefox上不起作用。这是解决此问题的另一种方法。

最初,我将字体大小设置为比需要的大得多。接下来,我检查文本长度是否大于路径长度的一半,如果是,则减小字体大小。我在while循环中这样做:

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>