Jump to content
Search Community

Continuous News Ticker with pause on hover?

ThePixelPixie test
Moderator Tag

Go to solution Solved by nico fonseca,

Recommended Posts

@nicofonseca - well, it was all working perfectly on Codepen. But when I moved it over to my WordPress site, it's *mostly* working correctly, except it only clones the set of items once. I tried playing with the "repeat: -1" and changed it to "repeat: 50", but it didn't make a difference. I have gsap.min.js enqueued after jquery and bootstrap, and have the separate function put in the footer, below the wp_footer() area where jquery, bootstrap, and gsap are placed by functions. It's scrolling, but it's only repeating once, and it's not stretching the full width like I have in this pen:

See the Pen vYZYbqj by ThePixelPixie (@ThePixelPixie) on CodePen


I could provide a link to the dev site in DM, but can't post the link here since it's for an organization I'm developing for.

Link to comment
Share on other sites

@Cassie This is the function I've got (with your bit commented out right now):

jQuery(function($) {
  $('ul.ticker li').wrapAll('<span class="ticker-items">');
  var tickerWidth = $('ul.ticker').width();
  var spanWidth = $('ul.ticker span').width();
  $('ul.ticker span').clone().appendTo('ul.ticker');
  $('ul.ticker span').wrapAll('<span class="ticker-wrapper">');
  gsap.set( '.ticker-wrapper' , {x: tickerWidth } );
  var tl = gsap.timeline( {repeat: -1} );
  tl.to( '.ticker-wrapper', {
    duration: 1,
    ease: Power0.easeNone,
    onComplete: function(){
      var style = window.getComputedStyle( $('.ticker-wrapper')[0] );
      var matrix = new WebKitCSSMatrix( style.webkitTransform );
      if( matrix.m41 < 0-spanWidth ){
        gsap.set( '.ticker-wrapper' , {x: 0} );
  //gsap.to('.ticker-wrapper', {
  	//	x: 50,
  	//	repeat: -1,
  	//	onRepeat: () => {
  	//		console.log('repeating')
  	//	}


Link to comment
Share on other sites

You definitely shouldn't be using WebKitCSSMatrix like that - it would be much cleaner (and more compatible) to use a simple gsap.getProperty(".ticker-wrapper", "x") but you don't even need to do that. Here's a different approach that leverages a simple xPercent animation: 

See the Pen rNwaLvw?editors=0010 by GreenSock (@GreenSock) on CodePen


$(document).ready( function(){
  $('ul li').wrapAll('<span class="ticker-items">');
  var tickerWidth = $('ul').width();
  var spanWidth = $('ul span').width();
  $('ul span').clone().appendTo('ul');
  $('ul span').wrapAll('<span class="ticker-wrapper">');
  var speed = 50,
      tl = gsap.timeline();
  tl.fromTo(".ticker-wrapper", { // intro (from far right edge)
    xPercent: tickerWidth / spanWidth / 2 * 100
  }, {
    xPercent: 0,
    duration: tickerWidth / speed,
    ease: "none"
  }).to(".ticker-wrapper", { // loop
    xPercent: -50,
    ease: "none",
    duration: spanWidth / speed,
    repeat: -1
  $('ul').on('mouseenter', () => gsap.to(tl, {timeScale: 0, overwrite: true}));
  $('ul').on('mouseleave', () => gsap.to(tl, {timeScale: 1, overwrite: true}));

Notice that in the mouseenter/mouseleave I'm actually animating the timeScale of the timeline so that it gradually slows down or speeds up instead of suddenly stopping/starting. I just think that feels nicer, but you're welcome to do a simple tl.pause() and tl.play() if you prefer. 


However, I think the problem you mentioned in your previous post has to do with the fact that only one clone is done in that code because it assumes your content fills the container. You could add a loop in there to clone it multiple times but I was inspired to create a gsap.effect.ticker that should help with this: 

See the Pen rNwaMPo?editors=0010 by GreenSock (@GreenSock) on CodePen


	name: "ticker",
	effect(targets, config) {
		let master = gsap.timeline();
		buildTickers(targets, config);
		function clone(el) {
			let clone = el.cloneNode(true);
			el.parentNode.insertBefore(clone, el);
			clone.style.position = "absolute";
			return clone;
		function nestChildren(el, className) {
			let div = document.createElement("div");
			while (el.firstChild) {
			className && div.setAttribute("class", className);
			div.style.display = "inline-block";
			div.style.boxSizing = "border-box";
			return div;
		function buildTickers(targets, config, isResize) {
			if (isResize) { // on window resizes, we should delete the old clones and reset the widths
				targets.clones.forEach(el => el && el.parentNode && el.parentNode.removeChild(el));
				gsap.set(targets.chunks, {x: 0});
			} else {
				targets.clones = [];
				targets.chunks = [];
			let clones = targets.clones,
				chunks = targets.chunks;
			targets.forEach((el, index) => {
				let chunk = chunks[index] || (chunks[index] = nestChildren(el, config.className)),
					chunkWidth = chunk.offsetWidth + (config.padding || 0),
					cloneCount = Math.ceil(el.offsetWidth / chunkWidth),
					chunkBounds = chunk.getBoundingClientRect(),
					elBounds = el.getBoundingClientRect(),
					right = (el.dataset.direction || config.direction) === "right",
					tl = gsap.timeline(),
					speed = parseFloat(el.dataset.speed) || config.speed || 100,
					i, offsetX, offsetY, bounds, cloneChunk, all;
				el.style.overflow = "hidden";
				gsap.getProperty(el, "position") !== "absolute" && (el.style.position = "relative"); // avoid scrollbars
				for (i = 0; i < cloneCount; i++) {
					cloneChunk = clones[i] = clone(chunk);
					if (!i) {
						bounds = cloneChunk.getBoundingClientRect();
						offsetX = bounds.left - chunkBounds.left;
						offsetY = bounds.top - chunkBounds.top;
					gsap.set(cloneChunk, {x: offsetX + (right ? -chunkWidth : chunkWidth) * (i + 1), y: offsetY});
				all = clones.slice(0);
				tl.fromTo(all, {
					x: right ? "-=" + (chunkBounds.right - elBounds.left) : "+=" + (elBounds.right - chunkBounds.left)
				}, {
					x: (right ? "+=" : "-=") + elBounds.width,
					ease: "none",
					duration: elBounds.width / speed,
					overwrite: "auto"
				}).to(all, {
					x: (right ? "+=" : "-=") + chunkWidth,
					ease: "none",
					duration: chunkWidth / speed,
					repeat: -1
				master.add(tl, 0);
			// rerun on window resizes, otherwise there could be gaps if the user makes the window bigger.
			isResize || window.addEventListener("resize", () => buildTickers(targets, config, true));
		return master;

Once that effect is registered, all you have to do is: 

let tl = gsap.effects.ticker(".ticker", {
  speed: 100,
  className: "ticker-content",
  //direction: "right"

So you can tweak the speed, direction, and the class name that's added to the wrapper <div> elements around the chunks. It dynamically figures out how many clones to make. It even handles resizes. You can even add data-speed or data-direction attributes to the elements instead of passing that in through the config object. 


I hope that helps!

  • Like 6
  • Thanks 1
Link to comment
Share on other sites

@Cassie - Yeah, if the marquee tag wasn't deprecated, I had one that was working great! So I guess this is the main workable alternative. I've tried so many different scripts, and none of them have come close to what marquee would do, except for this (so I'm very grateful)


@GreenSock - That all looks great. I don't know enough about GSAP yet to know what's what. I was literally just copying the code provided in the first response to my post above. I'll work on your suggestions this morning. Thanks SO much.

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...