HTML5 Canvas Running Texts with RSS

Canvas Running Texts with RSS
Canvas Running Texts with RSS
Image Source : Herbert Weber, Hildesheim, Kreuzgang san paolo fuori le mura 3, cropped, CC BY-SA 3.0

In this article “Canvas running texts with RSS” from our workshop series Digital Signage Newsticker we use the technique learned from the first Canvas article and fill the canvas context with content from a RSS feed. Since the algorithm for retrieving and processing the RSS feed was already extensively described in the article about CSS3 animations, we will only cover the specific differences.

The body region

The static HTML part needs some modifications, because we add more Javascript and want to make it as clear as possible.

<body onload="handleTicker()">
	<canvas id="myTicker" height="80">No Canvas</canvas>
</body>

In this demo we set the canvas with a height of 80px and an automatic width, because it should go over the complete display range. As identification we choose myTicker again. The onload event has already been described here and behaves identically in this example.

In the style sheet

<style>
	#myTicker
	{
		position: absolute;
		left: 0;
		top:0;	
	}
</style>

the element takes an absolute position starting at the upper left corner. That is important because this concept requires absolute positioning due to the calculations. A relative positioning would also not place the canvas exactly on the left and upper sides. The parent element body has by default a margin of 8px to its parent element html (browser window).

The complete javascript

window.addEventListener('resize', resizeCanvas, false);
const _move_pixel        = 1;
const _max_canvas_width  = 16384;
var MyCanvas	       = {};
var ctx	             = {};
var x                    = 0;
var ticker_content       = "";
var text_width           = 0;

function initCanvas()
{
	MyCanvas = document.getElementById("myTicker");
	ctx    = MyCanvas.getContext("2d");
}

function resizeCanvas()
{
	MyCanvas.width = window.innerWidth;
	ctx.font       = "30px Arial";
	text_width     = ctx.measureText(ticker_content).width;
	x              = MyCanvas.width;
}			

function isNewContentSizeValid(txt)
{
	return (ctx.measureText(txt).width  < _max_canvas_width);
}

function moveTicker()
{
	ctx.clearRect(0,0, MyCanvas.width, 80);
	if (x > -text_width)
		x = x - _move_pixel;
	else
		x = MyCanvas.width;
	ctx.fillText(ticker_content, x, 50);	
	window.requestAnimationFrame(moveTicker);
}

function displayTicker(ticker_text)
{
	ticker_content = ticker_text;
	resizeCanvas();
	window.requestAnimationFrame(moveTicker);
}

function createTickerOutput(feed_obj)
{
	var ticker_text = " +++ ";
	var tmp_text    = "";
	for (var i = 0; i < feed_obj.query.count; i++)
	{
		tmp_text =  feed_obj.query.results.item[i].title+ " +++ ";
		if (isNewContentSizeValid(ticker_text + tmp_text))
			ticker_text += tmp_text;
		else
			break;

	}
	return ticker_text;
}

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

function getRSS(url)
{
	var request_url = 'https://query.yahooapis.com/v1/public/yql?q=select title from rss where url="'+url+'"&format=json';
	var MyRequest = new XMLHttpRequest(); // a new request
	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);
	return;
}

function start()
{
	initCanvas();
	getRSS("https://smil-control.de/blog/feed");
}

Things get a little more complicated here. The canvas should behave flexibly and must adjust automatically when the width of the browser window changes. To achieve this, we must declare a so-called “event listener” in Javascript. When activated, it refers to a function that contains the commands required for resizing. We do this with window.addEventListener(‘resize’, resizeCanvas, false); This means that the callback function resizeCanvas() is always executed when resizing.

function resizeCanvas()
{
	canvas.width   = window.innerWidth;
	ctx.font       = "30px Arial";
	text_width     = ctx.measureText(ticker_content).width;
	x              = canvas.width;

}	

First, the canvas width get the size value from the new window width. Since the element resets itself, the font needs to be reset and the text width must be recalculated. In this case, the scrolling text starts again from the beginning. So we set the value of the position variable x to maximum again.
The constant and global variables are the same as in canvas_animations_2.html. However, with the difference that we initialize the variables for this sample with default values.

Get the RSS

In principle, the procedure is similar to the CSS3 animations. The entry function start() has remained the same, except for the additional initialization of the canvas and its context in initCanvas(). The general program sequence now has five steps:

  1. Initialize the canvas
  2. Get the RSS feed as JSON text
  3. Convert the JSON text into a Javascript object
  4. Extract the text for the ticker from the Javascript object
  5. Print the ticker text

There are two conceptual differences to the CSS3 animations

1. The good news: It is no longer necessary to calculate an animation time to control the reading speed depending on the text size. We determine the tempo of the running text by the number of pixels which the text should move with each frame. It therefore remains constant regardless of the text length. That means we removed the calculation function of the CSS3 animations.

2. The bad news: We have to solve another problem! The canvas width can’t expand to any size. The limits are different for each browser and may change in the future. This means, that we have to find out the values for the respective target device by trying them out. For example, Firefox currently limit to 22528 x 20992 pixels. With Chromium/Chrome the limit is 16384 x 16384 pixels. Depending on the length, this corresponds to 10 – 15 headlines. If our text exceeds the limit, the canvas context does not accept the content fillText() wants to insert and remains empty. This means that we must take precautions to deal with this problem.

Approaches to solve the limit issue

There are several ways to address this problem. For example, we could reduce the text from 30px to 20px. This can still work for feeds that are slightly over the limit. However, this is not a real solution, but only a bad workaround. The feed can still be too long.
A much more sustainable solution would be to fill the visible area of the canvas circularly. This means to delete a character that disappears from the canvas context on the left and to move a new character from the content on the right side. However, the algorithm for this is complex and would go outside the scope of this article. If you would like to have such a solution implemented, you are welcome to contact us.

We have decided to select what we consider to be a more pragmatic third option. We simply cut off the text before it reaches the limit. Most news feeds usually start with the latest news, while older ones slide down. If we assume that 10 – 15 headlines can be displayed from a feed, this should usually be up-to-date enough for a news ticker. Who wants to read outdated news?

Crop text before limit

Optimally, “cutting” and examining takes place in the function that merges the ticker content:

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

For each feed item, the system first verifies whether its addition exceeds the threshold value of 16384 pixels. The function isNewContentSizeValid() does this by examining the text length.

function isNewContentSizeValid(txt)
{
	return (ctx.measureText(txt).width < _max_canvas_width);
}

If it is smaller than 16384 pixels (_max_canvas_width), the function returns true. Thus, the headline is finally added to the ticker_text variable. If isNewContentSizeValid() returns false, this means that the addition will exceed the limit. In this case the condition is skipped and ticker_text remains unchanged. With break we also leave the for loop prematurely. You can check this by using the NTV feed (https://www.n-tv.de/rss) instead of https://smil-control.com/blog/feed The NTV feed usually has 20-22 entries and thus definitely exceeds the limit.

Canvas Running Texts with RSS – Wage of trouble

function displayTicker(ticker_text)
{
	ticker_content = ticker_text;
	resizeCanvas();
	window.requestAnimationFrame(moveTicker);
}

The global variable ticker_content receives the output text and the canvas is formed with the corresponding values. For this we use the resizeCanvas() function already created for resizing. At the end we launch the recursive animation function moveTicker(),

function moveTicker()
{
	ctx.clearRect(0,0, canvas.width, 80);
	if (x > -text_width)
		x = x - move_pixel;
	else
	x = canvas.width;
	ctx.fillText(ticker_content, x, 50);	
	window.requestAnimationFrame(moveTicker);
}

which has not changed significantly since the last post. Merely instead of the fixed value 500, the width value is now determined variably by the current canvas width.

Open the file canvas_animations_rss.html to get a live view. Depending on the connection speed, you may have to wait 1-4 seconds until the process ends.

What happens next?

In the next article we will introduce you two more techniques for horizontal scrolling texts. One of these is an element that belongs to the prehistoric age of the Internet. Then we will compare all concepts and summarize the individual advantages and disadvantages.
If you have any questions or comments, please do not hesitate to contact us.

Leave a Reply

Your email address will not be published. Required fields are marked *