CSS3 Running Texts

Horizontal auto scrolling texts are frequently requested functionalities in everyday digital signage. They are usually animated at the bottom of a multizone setup screen. In this article you will learn the technical basics for pure CSS animated running texts without Javascript using a simple example. At the end we fill the scrolling text with content from a RSS feed.

Introduction to the series of articles on running texts

This article starts a multi-part workshop to introduce different techniques for horizontal scrolling texts. Among others: HTML Canvas and SMIL. We will also explain in detail how to use Javascript to load any RSS feed, extract information and thus automatically keep the contents of your scrolling texts up to date. Finally, we will compare the techniques presented with advantages and disadvantages.

This workshop will be continued in the coming weeks. You don’t need to be a programmer to understand them, but you should have basic knowledge in HTML, CSS and Javascript.

CSS3 Running Texts
Classic Running Text
Bild: Herbert Weber, Kreuzgang, cropped, CC BY-SA 3.0

What is CSS Level 3??

CSS (Cascading Style Sheets) are a design and formatting language for websites. This allows you to create central styles to separate the design from the content. CSS Level 3 is the successor to the current Level 2 standard. Although it has been developed since 2000, Level 3 has not yet been fully adopted and recommendations exist only for some areas. Despite this, current browsers already support many CSS3 modules (April 2018). This can be explained by the rapid technical progress in the mobile sector, which is constantly demanding new functionalities.

In addition to modularization, namespaces, graphic filters and media queries, CSS3 brings the relevant transformations for animations with the @keyframes rule. CSS3 offers many advantages for digital signage applications. For example, we are able now to implement websites and widgets without using an additional programming language such as Javascript. Media queries have also made it easier to create web pages or HTML templates for different output devices with different resolutions or orientations simultaneously.

Preparing

But at first let us build the structure of the website to create the base for CSS3 running texts.

<html>
	<head>
		<title>SmilControl CSS</title>
		<style>
			#wrapper
			{
				width: 500px;
				border:1px solid #ddd;
				overflow: hidden;
		        }
			#ticker
			{
				position: relative;
				font: 30px Arial;
				padding:5px 0;
				white-space:nowrap;
			}		
		</style>
	</head>
	<body>
		<div id="wrapper">
			<div id="ticker">Lorem ipsum dolor sit amet, consetetur</div>
		</div>
	</body>
</html>

We nest two div’s into the body section. The div with id=“ticker“ represent the animated area. For a better overview, we set the wrapper div surrounding the ticker to a width of 500 pixels with a 1 pixel border. Overlong text displays no longer after 500px thanks to overflow:hidden. The ticker contains a 30px Arial font with the “Lorem ipsum dolor sit amet, consetetur” text.

A padding: 5px 0 in CSS ensures that there are 5 pixels distance to the border above and below the text. The position property is set to relative to control the animated transformations relative to the parent element. To ensure that the text is not wrapped in the ticker div, the white-space property must be nowrap.

Click on css3_animations_1.html to watch our first result.

What are keyframes?

Keyframes characterize an animation technique that originated in cartoons. In this genre, key images, sometimes also called waypoints, approximately define the sequence of movements and are completed by intermediate images as the work progresses. In CSS, animation sequences can also be defined by waypoints. These can be position or size information or even color values. To create a classic horizontal CSS3 running text, however, we only need position changes in the CSS style.

Animation with the @keyframes rule

A CSS animation is based on @keyframe rules. In the past we had to use so-called browser prefixes like -moz- -webkit- or -ie-. Meanwhile (May 2018) all current browser keyframes support keyframes by default. So let’s add the following under the #ticker style.

@keyframes moveTicker
{
	from
	{
		transform: translate3d(100%, 0%, 0px)
	}
	to
	{
		transform: translate3d(-100%, 0%, 0px);
	}
}

@keyframes introduces a rule called moveTicker. We insert two key images here. The first state (from) and the last state (to). The first keyframe of the animation moves the value of the x-property of the text to 100% using translate3d. I.e. the text now starts invisibly for us on the right side of the wrapper div. We will explain below why we use translate3d. The last key frame should be set to -100% left. This means that the text is invisibly located on the left side of the wrapper div.

To activate the animation you have to insert

#ticker
{
	...
	animation: moveTicker 10s linear infinite; 
}

into the CSS stylesheet of the ticker.

If the animation (moveTicker) starts now, the text scrolls within 10s with constant speed (linear) to the left side of the wrapper div. As soon as the last letter has run out of the image (-100%) the animation starts again (infinite).

Congratulations! You animated your first ticker.

Click on css3_animations_2.html for a demonstration.

Technical choices for CSS3 running texts

Maybe some of you will wonder why we are using transform and on top of that transform3d. CSS3 running texts would work with:

@keyframes moveTicker
{
	from
	{
		left:100%;
	}
	to
	{
		left:-100%;
	}
}

als auch mit

@keyframes moveTicker
{
	from
	{
		transform: translateX(100%);
	}
	to
	{
		transform: translateX(-100%);
	}
}		

All three CSS-variants are completely correct, but can have different performance affects. The bad news is: you have to find out yourself which keyframe variant is best suited for your hardware. For a long time transformations were contemplated more performant to position changes than left/top.

However, this no longer generally applies.here it was found that left/top offers more efficiency under certain conditions. Ultimately, only trying it out will help. In the example with rss feed we will see that it can even make sense to combine left and transform for CSS3 running texts.

The 3D acceleration trick

The variant we chose with translate3d in CSS style forces the browser or webview to activate the so-called GPU acceleration for WebGL. WebGL is the standardized 3D interface for web browsers. This means that the GPU performs the calculations instead of the CPU. Of course only if a corresponding device with working drivers is integrated.

The trick with the GPU acceleration ideally leads to softer scrolling with less system load. Unfortunately, this is not guaranteed, varies from hardware to hardware, and the software must also be compatible. For example, the WebView of an Android up to version 4.x does not support 3D acceleration at all, even if the chip would be able to do so. WebGL acceleration is only available from Android 5 by default. These script can help you to find out if translate3d or translateX is more performant for your Digital Signage Hardware/Software combination.

However, there is one small disadvantage: translate3d does not understand any percentages values on its z-axis. This is fortunately irrelevant for our scrolling text application, as we only want to move the x-axis. Thus the z-value must be 0px.

A relativizing note

All current PC web browsers should accelerate CSS3 animations in hardware generally. The differences are at least marginal with Chromium 65 on Linux. Nevertheless we have addressed this topic in such detail, because the latest operating systems are not always used on digital signage media players. Many inexpensive media players are shipped with older Android versions.

You will probably be able to buy an Rk3288 player chip in 2018 with Android 5.1.1 at most. The webview used may also differ. In this respect, it makes sense to use the background knowledge provided here to take the time and find out which variant works most efficiently for you. From my experience I know how sensitive customers are to jerky tickers.

The next Level

CSS-Running Text with RSS
CSS-Running Text with RSS

In the last example we created a simple scrolling text for illustration. However, everyday Digital Signage challenges us more than this. If the text comes from an RSS feed, the text length and velocity are variable. It is also unknown which horizontal resolution the device has.

That’s why we need to be flexible in our design. Furthermore, we now face a problem with the security policy of web browsers. The so-called SOP (Same-Origin-Policy) does not allow to call Javascript or CSS content from another domain than the currently. We will take these hurdles one by one.

By the way, for a more detailed explanation of feed formats like RSS and Atom, see the article on Channels with SVG and Feeds.

A more flexible design concept for CSS3 running texts with RSS

Do you remember css3_animations_2.html. The CSS3 scrolling texts with RSS Feed should now span the entire width of the page. This means that we change the CSS style area to the following:


<style>
	#ticker
	{
		position: relative;
		font: 30px Arial;
		white-space: nowrap;
		animation: moveTicker 10s linear infinite;
		display: inline-block;	
		
	}
	@keyframes moveTicker
	{
		from
		{
			transform: translate3d(0%, 0%, 0px);
			left: 100%;
		}
		to
		{
			transform: translate3d(-100%, 0%, 0px);
			left: 0%;
		}
	}		
</style>

and the body area to:

<body onload="start()">
	<div id="ticker"></div>
</body>

A wrapper div is now unnecessary and has been revomed. Since the ticker-div should get its text from the later Javascript, it stays empty at first. The onload event in the body tag is explained below.

There are some changes in the stylesheet for the ticker. The animation time will be set later depending on the number of headlines and is not relevant here.

As mentioned at the beginning, we do not know which horizontal resolution will be available in future for the display unit. Without the new changes in the CSS, the ticker div would be set to the width of the browser window. This is usually much smaller than the width of the actual text. This means, that the scrolling text never runs completely because it always jumps back to the starting point in the middle and starts again.

To prevent this from happening, the display style of the element is set as an inline block. Thus horizontal positioning can be used (analog display:block), but the element behaves like a span element, for example. These elements have no interlace, no definable width and can therefore be on the same line as other inline elements. This is important, so that the combination of a position specification (left) and the transformation (translate3d) can do the “magic” together.

Transformations with position specification

Position information and transformations behave differently. The specification “left” refers to the parent element, but “translate” refers to the element itself. This is particularly important, cause we need to specify percents values.

An example: If we would set a transform: translate3d(100%, 0%, 0px) which seems to be identical to “left:100%” at from-range, the text starts much too far to the right in the off. With each “run out” on the left, it takes a long time for the text on the right to start again.

The 100% of the ticker element are absolutely (in pixels) larger than the 100% of the browser window. With left:100% and translate3d(0%, 0%, 0px) we ensure that the starting point is positioned exactly at 100% of the browser width. But that is not enough. The text does not run through completely in browser as described above and starts again too early. So we have to explicitly declare the end position as left:0% with -100% transformation to ensure a complete cycle.

Events in the body-tag

To start the Javascript processing only after the HTML part is completely loaded, we use the event onLoad in the body tag. This event calls the start() function, which initiates a process, which we will describe in more detail below.

The Javascript Algorithm

At the beginning we construct an algorithm consisting of four steps.

  1. Step: Get the RSS as JSON-Text
  2. Step: Convert the JSON response to a javascript object
  3. Step: Extract the ticker text from javascript object
  4. Step: Display the ticker

JSON (JavaScript Object Notation) is a standard text format for the uncomplicated exchange of structured data. It is less readable than XML, but has less overhead. Moreover, Javascript can efficiently convert JSON into objects.

Bypass SOP

Background on this Technique

Reloading javascripts or data from other domains/origin represents a critical attack vector. For example, the other server could be hacked. If other servers and web browsers access it, an attacker can effectively compromise all users by piggybacking on malicious code. The SOP is designed to prevent that. In our scenario, however, unfortunately it stands largely in the way.

Ring Road Tricks

.

For the examples we use our own RSS feed at https://smil-control.com/blog/feed/. There are several solutions for querying this without coming into conflict with the browser security concept (Same Origin Policy) mentioned above. E.g. CORS (Cross-Origin Resource Sharing), JSONP or even a separate server-side script.

However, these solutions have their advantages and disadvantages and require additional work whose explanations would go beyond the scope of this tutorial. Therefore we decided to use the Yahoo Query Language (YQL). Yahoo makes this interface freely available to the general public at https://developer.yahoo.com/yql. For example: The request ‘select title, description from rss where url=”https://smil-control.com/blog/feed”&format=json’ delivers us all title and description tags of the feed in JSON format.

Update: This Yahoo-Api and YQL have been discontinued. So I wrote a small replacement script and published it in github. The script is mostly compatible to the Yahoo-Api. Therefore it can be used with this tutorial. This is still more comfortable than JSONP or CORS.

1. Step: Get the RSS as JSON-Text

The function getRSS is called with the url of the RSS feed and looks like this:

function getRSS(url)
{
	var request_url = 'https://smil-control.de/beispiele/fetch-rss.php?feed_url='+url;
	var MyRequest = new XMLHttpRequest();
	MyRequest.open("GET", request_url, true);
	MyRequest.onload = function (e)
	{
		if (MyRequest.readyState === 4)
		{
			if (MyRequest.status === 200)
			{
				handleTicker(MyRequest.responseText);
			}
			else
			{
				console.error(MyRequest.statusText);
			}
		}
	};
	MyRequest.onerror = function (e)
	{
		console.error(MyRequest.statusText);
	};
	MyRequest.send(null);
}

First, the query url is assembled. Since only the title as headline is relevant for the scroll bar, we only request that. New XMLHttpRequest() creates a new object (here: MyRequest) for a data query via an url. The function open() initializes a GET request asynchronously and finally sends it (send()). GET is a simple HTTP request to request data from a server. In principle it is like entering something in the address bar of your browser.

What means asynchronous

Asynchronous means that after the query is sent, the script does not wait for the response to continue. These are the so-called AJAX queries common in modern web applications. Asynchronous queries are executed in the background and do not block the browser. It can then continue with other things, such as drawing up the page layout.

Event Handler

Furthermore, we create two so-called event handler functions. Onerror() is activated if there is an error e.g. the Yahoo-Url is not reachable. If this happens, we write the error message in the console. The onload() function checks whether a result is available. I.e. the readyState property of the query takes the value 4.

Next, the so-called status code of the response is checked by Yahoo the script. If everything is fine, the status code is set to 200 and the script passes the text of the response to the handleTicker() function for further processing. Otherwise, an error message is displayed in the console. />

Web server queries and responses

By default, web servers send a response to a query with a numeric status code. If successful, this is 200. Errors start with a 4 or 5. You have certainly called up a page before that was not there. The browser window then displays the "famous" error message 404.

For programmers, this is convenient. We can simply check if the server is responding correctly.

The handleEvent function now takes over the further steps of the algorithm determined above.

function handleTicker(response)
{
	var feed_obj    = JSON.parse(response);
	var ticker_text = createTickerOutput(feed_obj);
	displayTicker(ticker_text, feed_obj.query.count);			
}

2. Step: Convert the JSON response to a javascript object

The text in JSON format passed by the getRSS() function is converted to a Javascript object with JSON.parse(response). Attention! Old tutorials may refer to eval(), since JSON.parse was only introduced as ECMA 5.1 standard in December 2009. Eval() can be used to execute Javascript code and convert JSON to objects. Please try to avoid using it, because it also carries the risk of unintentionally infiltrating malicious code. JSON.parse() is more secure and executes faster.

I hope it's only a matter of time before the eval() function is kicked out of the ECMA standardization. After all, that's one of the main reasons for javascript's bad reputation.

3. Step: Extract the ticker text from javascript object


function createTickerOutput(feed_obj)
{
	var ticker_text = "";
	for (var i = 0; i < feed_obj.query.count; i++)
	{
		ticker_text += feed_obj.query.results.item[i].title+ " +++ ";
	}
	return ticker_text;
}

The function loops through all messages (items) of the feed object step by step. The title field for the headline is extracted and added to the ticker_text string. In ticker_text all headlines are separated by a “+++” after the end of the loop. The content of the ticker_text variable is returned and will be used for the scrolling text.

Determine the animation time

The duration of an animation must be set in the stylesheet. A fixed time can cause problems with different text lengths. CSS3 running texts with RSS could contain 20 headlines and more. These are certainly not easy to read in 10 seconds. On the other side: If the time is too high, the ticker scrolls painfully slowly. This can be compensated by a functionality that determines the animation time depending on the number of headings:


function getAnimDurationInSeconds(num)
{
	var seconds = num*10;
	return seconds.toString()+"s";
}

To calculate the animation time, we apply 10s reading time per headline. Five headlines, that’s 50 seconds. Good headlines usually consist of 4-8 words. So 10 seconds time to read a smooth scrolling text should be approximately appropriate. You can also “play around” with this factor until the speed meets your requirements.

4. Step: Wage of trouble

In the last step we fill the ticker-div with the text extracted above and set the animation time in seconds.

function displayTicker(ticker_text, num_headlines)
{
	var ticker                     = document.getElementById("ticker");
	ticker.innerHTML               = ticker_text;
	ticker.style.animationDuration = getAnimDurationInSeconds(num_headlines);
} 

First we initialize a variable called ticker to address the ticker-div. Next, the div gets the ticker text with the headlines and finally we set the calculated animation time in the CSS.

Click on css_animation_rss.html, to view the CSS3 running texts with RSS. You will see that the ticker always behaves the same. Even if you change the width of your browser window during the process.

What happens next?

The topi of the next post is HTML Running Texts. we will create a horizontal scrolling text based on HTML5 canvas and fill it also with content from an RSS feed.

If you have any questions or comments to CSS animations or running fonts, please do not hesitate to contact me.


Gravatar Nikolaos Sagiadinos
Written by Nikolaos Sagiadinos

Specialized on Digital Signage Software Solutions

Visit me on: LinkedIn und Xing

Contact form

We have attracted your interest?
Simply contact us.

Our contact data

SmilControl OHG
Niederaue 1a
D-30419 Hanover

Germany

☎ +49 (0) 511 - 96 499 560

Local court Hanover
HRA 202345
VAT-Id: DE 281 780 194

Authorized to represent:
Nikolaos Sagiadinos, Mohammed Kabiri