Radial Menu

Today’s article is inspired by the Radial Menu found in many of Bioware’s RPGs, such as Dragon Age. At the press of a button, the menu appears on the screen (usually pausing the game), and allows players to choose common commands. The same type of menu is possible on the web, and may even be more useful than the traditional navigation. We will use JavaScript, CSS, and YUI3 to build flexible Radial Menu widget that appears in the center of the page and responds to end-user clicks.

How do it…

You will need the following 3 files:

You will need to include the following code on your page:

<link type="text/css" href="http://yui-ext-mvc.googlecode.com/svn/trunk/assets/css/radialMenu.css" rel="stylesheet"/>
<script type="text/javascript">YUI({
	filter:'raw',
	modules: {
		'gallery-radial-menu': {
			fullpath: 'http://yui-ext-mvc.googlecode.com/svn/trunk/assets/js/yahoo-3-ext/RadialMenu.js',
			requires: ['widget', 'radialMenuPanel'],
			optional: [],
			supersedes: []
		},
		'gallery-radial-menu-panel': {
			fullpath: 'http://yui-ext-mvc.googlecode.com/svn/trunk/assets/js/yahoo-3-ext/RadialMenuPanel.js',
			requires: ['base'],
			optional: [],
			supersedes: []
		}
	}
}).use('gallery-radial-menu', 'gallery-radial-menu-panel', function(Y) {
/* instantiate RadialMenu here */
})</script>

Instantiate the Radial Menu:

var radioMenu = new Y.RadialMenu({boundingBox: '#aDivAppendedToBody', diameter: 200, panels: [
	new Y.RadialMenuPanel({styles: {backgroundColor: 'yellow'}, content: '0'}),
	new Y.RadialMenuPanel({styles: {backgroundColor: 'green'}, content: '1'}),
	new Y.RadialMenuPanel({styles: {backgroundColor: 'blue'}, content: '2'}),
	new Y.RadialMenuPanel({styles: {backgroundColor: 'purple'}, content: '3'}),
	new Y.RadialMenuPanel({styles: {backgroundColor: 'red'}, content: '4'}),
	new Y.RadialMenuPanel({styles: {backgroundColor: 'orange'}, content: '5'})
]});
radioMenu.render();
radioMenu.subscribe('panelClicked1', function() {
	/* do something if panel 1 is clicked */
});
radioMenu.subscribe('panelClicked', function() {
	/* do something if any panel is clicked */
});

How it works…

The RadialMenu file does most of the work, handling the rendering and managing events by extending the YUI3 widget framework. The RadialMenuPanels are simple objects extending YUI3 base framework, used to render and manage information about each panel, including: styles, content, tagName. The RadialMenu CSS file is used to provide basic CSS support for displaying the widget.

When the RadialMenu is show, the boundingBox fills up the viewport, so the user can only interact with the menu. Inside the contentBox the RadialMenuPanels are drawn around a circle with the diameter you specified (or defaults to 100). The diameter is measured from the center of the each pane and each panel uses basic trigonometry to determine its position around the circle.

The trigonometry equation for each point is:

var x = centerOfScreen.x * radius * cos(angle);
var y = centerOfScreen.y * radius * sin(angle);

Each angle is determined by dividing the number of available degrees in a circle by the number of panels (so 360 / n), then multiplying it by the panels position in the cirlce (so position 3 of 8 panels would be, 360 / 8 * 3).

The hover effect is handled via JavaScript mousemove event and applying the class radialMenuPanel-hover to the hovered panel. The
mousemove event of the contentBox node is listened to and event propagation is used to determine if a panel is being hovered over or not. Each panel has a z-index of 3 + its position, but the hover class overrides the z-index with 100 so that the hovered panel appears on top of the others.

A click event is listened for on the code>contentBox as well, which uses propagation to determine which panel was clicked. When a panel is clicked it fires two events panelClicked and panelClickedN (where N is the position of the panel), allowing developers to subscribe to any panel clicked or to a specific panel click. By default anytime the user clicks when the RadialMenu is opened, the RadialMenu closes, regardless of whether a panel is clicked or not. To turn this off, set the configuration property closeOnClick to false.

There’s more…

I have put together a YUI 3 Radial Menu Gallery Component so you can test out the widget. It is very simple, only rendering panels with colored backgrounds, but allows you to configure the number of panels and the size of the diameter.

More To Come…

There are three major improvements I would like to make to this widget. The first is to leverage the existing overlay infrastructure that is built into YUI3. This way we can leverage existing shimming and positioning architecture. The second is to animate the menu. I believe the menu should rotate out from the center when opened and rotate in the center when closed. Lastly, the menu needs keyboard support. Although the keyboard is not the same as a gaming controller, I believe the RadialMenu lends itself to keyboard interaction.

Additionally, I wanted to create a skin or two for the widget, but ran out of time. My ultimate goal is to replicate a menu used by a video game, to show how flexible this infrastructure is.