Home > Net >  SVG is shaking (flicking) on margin-left change (marquee effect)
SVG is shaking (flicking) on margin-left change (marquee effect)

Time:12-14

I'm trying to use a marquee effect with vanilla JS. The effect works, but svg (and images) are shaking on move.

<div >
  <h1>Nepal <svg version="1.1" viewBox="0 0 36 31" xmlns="http://www.w3.org/2000/svg"><path d="M8.80476 8.17844V12.3988H3.9993V4.25693H8.79846V8.17844H8.80476ZM32.4101 3.87271V0H20.0028V3.87271H16.0035V0H3.60252V3.87271H0V15.497H3.60252V19.76H7.60182V23.2485H11.6011V27.5115H15.6004V31H20.3996V27.5115H24.3989V23.2485H28.3982V19.76H32.3975V15.497H36V3.87271" fill="#DA2269"/></svg> Himalayas <svg version="1.1" viewBox="0 0 36 31" xmlns="http://www.w3.org/2000/svg"><path d="M8.80476 8.17844V12.3988H3.9993V4.25693H8.79846V8.17844H8.80476ZM32.4101 3.87271V0H20.0028V3.87271H16.0035V0H3.60252V3.87271H0V15.497H3.60252V19.76H7.60182V23.2485H11.6011V27.5115H15.6004V31H20.3996V27.5115H24.3989V23.2485H28.3982V19.76H32.3975V15.497H36V3.87271" fill="#DA2269"/></svg> Mountains <svg version="1.1" viewBox="0 0 36 31" xmlns="http://www.w3.org/2000/svg"><path d="M8.80476 8.17844V12.3988H3.9993V4.25693H8.79846V8.17844H8.80476ZM32.4101 3.87271V0H20.0028V3.87271H16.0035V0H3.60252V3.87271H0V15.497H3.60252V19.76H7.60182V23.2485H11.6011V27.5115H15.6004V31H20.3996V27.5115H24.3989V23.2485H28.3982V19.76H32.3975V15.497H36V3.87271" fill="#DA2269"/></svg> Everest</h1>
</div>

<style>
.marquee {
  overflow: hidden;
  border-top: 1px solid #000;
  border-bottom: 1px solid #000;
  display: flex;
}

.marquee h1{
  font-size: 5em;
  white-space: nowrap;
  text-transform: uppercase
}

.marquee h1 svg {
  width: auto;
  height: 60px;
}
</style>

<script>
function Marquee(selector, speed) {
  const parentSelector = document.querySelector(selector);
  const clone = parentSelector.innerHTML;
  const firstElement = parentSelector.children[0];
  let i = 0;
  console.log(firstElement);
  parentSelector.insertAdjacentHTML('beforeend', clone);
  parentSelector.insertAdjacentHTML('beforeend', clone);

  setInterval(function () {
    firstElement.style.marginLeft = `-${i}px`;
    if (i > firstElement.clientWidth) {
      i = 0;
    }
    i = i   speed;
  }, 0);
}

//after window is completed load
//1 class selector for marquee
//2 marquee speed 0.2
window.addEventListener('load', Marquee('.marquee', 0.2))

</script>

Example: https://codepen.io/diegosomar/pen/dyKBGWg

Someone knows if there's a way to prevent svg shaking?

CodePudding user response:

If you set your Interval to update in 100ms like this:

setInterval(function () {
    firstElement.style.marginLeft = `-${i}px`;
    if (i > firstElement.clientWidth) {
      i = 0;
    }
    i = i   speed;
  }, 100);

You will see clearly what is going on. Not only do the images shake, but individual letters also shake. This shake effect seems to be how browsers (at least Chrome) render images/letters.

However, I noticed something when looking at the CSS. When I changed the width to width: 70px, the 'shaking' seemed to stop (I chose 70px because it was closer to the initial heart image size). When I examined the Heart SVG element on the Inspector, I saw that the width's changing process had a delay when it was set to width: auto compared to width: 70px. This made it appear to jitter more.

Using fixed width size in pixels is a hacky solution to the problem, but there is a much better and simpler approach to make the effect with CSS that might solve the issue.

Instead of changing margin-left every millisecond, you can simply change it every second, and let the smooth transition be handled by CSS. You can learn about CSS transitions here.

<style>
.marquee h1 {
  transition: margin-left 1s linear;
}
</style>

<script>
function Marquee(selector, speed) {
  const parentSelector = document.querySelector(selector);
  const clone = parentSelector.innerHTML;
  const firstElement = parentSelector.children[0];
  let i = 0;
  console.log(firstElement);
  parentSelector.insertAdjacentHTML('beforeend', clone);

  setInterval(function () {
    firstElement.style.marginLeft = `-${i}px`;
    if (i > firstElement.clientWidth) {
      i = 0;
    }
    i = i   speed;
  }, 1000); //update every second
}

window.addEventListener('load', Marquee('.marquee', 100)) //every second -100 pixels will be added to `margin-left` of the element
</script>

By adding transition to .marquee h1 style, you can see the difference right away. Note that you may want to handle the transition after i = 0 to prevent the sudden smooth jump from -2000px (the firstElement.clientWidth size) to 0px.

CodePudding user response:

Replace setInterval() with requestAnimationFrame()

As pointed out in @Jefferson's answer you current setInterval() call has a zero millisecond delay causing a jittery animation/transition.
Besides, it results in inconsistent timings e.g. in Firefox and Chromium.

requestAnimationFrame() ensures, your style changes are applied once per frame – whereas, setInterval() might try to change style properties "in between" frames (if the interval in milliseconds doesn't match a frame).

Example snippet

function Marquee(selector, speed) {
  const parentSelector = document.querySelector(selector);
  const clone = parentSelector.innerHTML;
  const firstElement = parentSelector.children[0];
  let i = 0;
  parentSelector.insertAdjacentHTML('beforeend', clone);
  parentSelector.insertAdjacentHTML('beforeend', clone);

  function scrollMarquee(timestamp) {
    timestamp = timestamp || new Date().getTime();
    firstElement.style.marginLeft = `-${i}px`;
    if (i >= firstElement.clientWidth ) {
      i = 0;
    }
    i  = speed;
    requestAnimationFrame(scrollMarquee); 
  } 
  requestAnimationFrame(scrollMarquee);
}

//after window is completed load
//1 class selector for marquee
//2 marquee speed 0.2
window.addEventListener('load', Marquee('.marquee', 1))
.marquee {
  overflow: hidden;
  border-top: 1px solid #000;
  border-bottom: 1px solid #000;
  display: flex;
}

.marquee h1{
  font-size: 5em;
  white-space: nowrap;
  text-transform: uppercase
}

.marquee h1 svg {
  width: auto;
  height: 60px;
}
<div >
  <div id="marquee" >
    <h1> Nepal <svg viewBox="0 0 36 31">
        <path d="m9 8v4h-5v-8h5v4l0 0zm23-4v-4h-12v4h-4v-4h-12v4h-4v11h4v5h4v3h4v5h4v3h4v-3h4v-5h4v-3h4v-5h4v-11" fill="#DA2269" />
      </svg> Himalayas <svg viewBox="0 0 36 31" xmlns="http://www.w3.org/2000/svg">
        <path d="m9 8v4h-5v-8h5v4l0 0zm23-4v-4h-12v4h-4v-4h-12v4h-4v11h4v5h4v3h4v5h4v3h4v-3h4v-5h4v-3h4v-5h4v-11" fill="#DA2269" />
      </svg> Mountains <svg viewBox="0 0 36 31" xmlns="http://www.w3.org/2000/svg">
        <path d="m9 8v4h-5v-8h5v4l0 0zm23-4v-4h-12v4h-4v-4h-12v4h-4v11h4v5h4v3h4v5h4v3h4v-3h4v-5h4v-3h4v-5h4v-11" fill="#DA2269" />
      </svg> Everest</h1>
  </div>
</div>

Prefer integer values for animations – if feasible

Incrementing style properties by floating point numbers can cause rounding errors – also resulting in undesired jittery transitions and performance issues.
This also applies to svgs as described in this codepen article.

  • Related