Chapter 3. The HTML5 Canvas Text API
The HTML5 Canvas Text API allows developers to render text on an HTML page in ways that were either tricky or next to impossible before its invention.
We are providing an in-depth analysis of the HTML5 Canvas Text API because it is one of the most basic ways to interact with the canvas. However, that does not mean it was the first Canvas API feature developed. In fact, for many browsers, it was one of the last parts implemented.
There was a time in the recent past when HTML5 Canvas Text API support in browsers was spotty at best. Back then, using modernizr.js to test for text support would have been a good idea. However, at this historic moment, all modern browser versions (besides IE) support the HTML5 Canvas Text API in some way.
This chapter will create an application named “Text Arranger” to demonstrate the features and interdependencies of the HTML5 Canvas Text API. This application will display a single line of text in an almost infinite number of ways. This is also a useful tool to see whether support for text is common among web browsers. Later in this chapter you will see that some text features are incompatible when drawn on the canvas at the same time.
Displaying Basic Text
Displaying text on HTML5 Canvas is simple. In fact, we covered the very basics in Chapter 1. First, we will review these basics, and then we will show you how to make them work with the Text Arranger application.
Basic Text Display
The simplest way to define text to be displayed on the canvas is
to set the context.font
style using standard values
for CSS font style attributes: font-style
, font-weight
, font-size
, and font-face
.
We will discuss each of these attributes in detail in the upcoming section Setting the Text Font. All you need to know now is that a font designation of some type is required. Here is a simple example of setting a 50-point serif font:
context.font = "50px serif";
You also need to set the color of the text. For filled text, you
would use the context.fillStyle
attribute and set it
using a standard CSS color, or with a Canvas
Gradient
or CanvasPattern
object. We will discuss the
latter two options later in the chapter.
Finally, you call the context.fillText()
method, passing the text to
be displayed and the x
and y
positions of the text on the
canvas.
Below is an example of all three basic lines of code required to display filled text on HTML5 Canvas:
context.font = "50px serif" context.fillStyle = "#FF0000"; context.fillText ("Hello World", 100, 80);
If you do not specify a font, the default 10px sans-serif will be used automatically.
Handling Basic Text in Text Arranger
For Text Arranger, we are going to allow the user to set the text
displayed by the call to context.fillText()
. To do this, we will create
a variable named message
where we
will store the user-supplied text. We will later use that variable in
our call to context.fillText()
, inside the standard
drawScreen()
method that we
introduced in Chapter 1 and will
continue to use throughout this book:
var message = "your text
";
...
function drawScreen() {
...
context.fillStyle = "#FF0000";
context.fillText (message, 100, 80);
}
To change the text displayed on the canvas to the text entered by
the user, we need to create an event handler for the text box keyup
event. This means that whenever someone
changes text in the box, the event handler function will be
called.
To make this work, we are going to name our text box in our HTML
<form>
using an <input>
form element. Notice that the id
is set to the value textBox
. Also
notice that we have set the placeholder=""
attribute. This attribute is
new to HTML5, so it might not work in every browser. You can also
substitute it with the value=""
attribute, which will not affect the execution of this
application:
<form>
Text: <input id="textBox" placeholder="your text
"/>
<br>
</form>
Communicating Between HTML Forms and the Canvas
Back in our JavaScript code, we need to create an event handler
for the keyup
event of textBox
. We do this by finding the form
element using the document.getElementById()
function of the DOM
document
object, and storing it in
the formElement
variable. Then we
call the addEventListener()
method of
formElement
, setting the event to
keyup
and the event handler to the
function textBoxChanged
, which we
have yet to define:
var formElement = document.getElementById("textBox"); formElement.addEventListener('keyup', textBoxChanged, false);
The final piece of the puzzle is to define the textBoxChanged()
event handler. This function
works like the event handlers we created in Chapter 1. It is passed one parameter
when it is called, an event
object
that we universally name e
because
it’s easy to remember.
The event
object contains a
property named target
that holds a
reference to the HTML form element that created the change
event. In turn, the target
contains a property named value
that holds the newly changed value of
the form element that caused the event to occur (i.e., textBox
). We retrieve this value, and store it
in the message
variable we created in
JavaScript. It is the very same message
variable we use inside the drawScreen()
method to paint the canvas. Now, all we have to do is call draw
Screen()
, and the new value of message
will appear “automagically” on the
canvas:
function textBoxChanged(e) { var target = e.target; message = target.value; drawScreen(); }
We just spent a lot of time describing how we will handle changes in HTML form controls with event handlers in JavaScript, and then display the results on an HTML5 Canvas. We will repeat this type of code several more times while creating Text Arranger. However, we will refrain from explaining it in depth again, instead focusing on different ways to render and capture form data and use it with Canvas.
Using measureText
The HTML5 Canvas context object includes a useful method, measureText()
. When supplied with a text
string, it will return some properties about that text based on the
current context settings (font face, size, etc.) in the form of a
TextMetrics
object. Right now the
TextMetrics
object has only a single
property: width
. The width
property of a TextMetrics
object gives you the exact width
in pixels of the text when rendered on the canvas. This can be very
useful when attempting to center text.
Centering text using width
For the Text Arranger application, we will use the TextMetrics
object to center the text the
user has entered in the textBox
form control on the canvas. First, we retrieve an instance of TextMetrics
by passing the message
variable (which holds the text we
are going to display) to the measureText()
method of the 2D context, and
storing it in a variable named metrics
:
var metrics = context.measureText(message);
Then, from the width
property
of metrics
, we get the width
of the text in pixels and store it in
a variable named textWidth
:
var textWidth = metrics.width;
Next, we calculate the center of the screen by taking the
width
of the canvas and dividing it
in half (theCanvas.width/2
). From
that, we subtract half the width
of
the text (textWidth/2
). We do this because text
on the canvas is vertically aligned to the left when it is displayed
without any alignment designation (more on this a bit later). So, to
center the text, we need to move it half its own width to the left,
and place the center of the text in the absolute center of the canvas.
We will update this in the next section when we allow the user to
select the text’s vertical alignment:
var xPosition = (theCanvas.width/2) - (textWidth/2);
What about the height of the text?
So, what about finding the height of the text so you can break
text that is longer than the width of the canvas into multiple lines,
or center it on the screen? Well, this poses a problem. The TextMetrics
object does not contain a height
property. The text font size does not give the full picture either, as
it does not take into account font glyphs that drop below the baseline
of the font. While the font size will help you estimate how to center
a font vertically on the screen, it does not offer much if you need to
break text onto two or more lines. This is because the spacing would
also need to be taken into account, which could be very
tricky.
For our demonstration, instead of trying to use the font size to
vertically center the text on the canvas, we will create the yPosition
variable for the text by simply
placing it at one-half the height of the canvas. The default baseline
for a font is middle
, so this works great for centering on
the screen. We will talk more about baseline
in the next section:
var yPosition = (theCanvas.height/2);
Note
In the chat example in Chapter 11, we will show you an example of breaking up text onto multiple lines.
fillText and strokeText
The context.fillText()
function
(as shown in Figure 3-1) will render solid
colored text to the canvas. The color used is set in the context.fillColor
property. The font used is
set in the context.font
property. The
function call looks like this:
fillText([text],[x],[y],[maxWidth]);
where:
text
The text to render on the canvas.
x
The
x
position of the text on the canvas.y
The
y
position of the text on the canvas.maxWidth
The maximum width of the text as rendered on the canvas. At the time of this writing, support for this property was just being added to browsers.
The context.strokeText()
function (as shown in Figure 3-2) is similar, but it
specifies the outline of text strokes to the canvas. The color used to
render the stroke is set in the context.strokeColor
property; the font used is
set in the context.font
property. The
function call looks like:
strokeText([text],[x],[y],[maxWidth])
where:
text
The text to render on the canvas.
x
The
x
position of the text on the canvas.y
The
y
position of the text on the canvas.maxWidth
The maximum width of the text as rendered on the canvas. At the time of this writing, this property does not appear to be implemented in any browsers.
The next iteration of Text Arranger adds the ability for the user
to select fillText
, strokeText
, or both
. Selecting both
will give the fillText
text a black border (the strokeText
). In the HTML
<form>
, we will add a <select>
box with the id fillOrStroke
, which will allow the user to
make the selections:
Fill Or Stroke: <select id = "fillOrStroke"> <option value = "fill">fill</option> <option value = "stroke">stroke</option> <option value = "both">both</option> </select>
In the canvasApp()
function, we
will define a variable named fillOrStroke
that we will use to hold the
value selected by the user on the HTML <form>
. The default value will be
fill
, which means Text Arranger will
always show fillText
first:
var fillOrStroke = "fill";
We will also create the event listener for a change in the
fillOrStroke
form element…
formElement = document.getElementById("fillOrStroke"); formElement.addEventListener('change', fillOrStrokeChanged, false);
…and create the function fillOrStrokeChanged()
to handle the
event:
function fillOrStrokeChanged(e) { var target = e.target; fillOrStroke = target.value; drawScreen(); }
In the drawScreen()
function,
we test the fillOrStroke
variable to
see whether it contains the value fill
. Since we have three states (fill
, stroke
, or both
), we use a switch
statement to handle the choices. If the
choice is both
, we set the strokeStyle
to black (#000000
) as the highlight for the colored
fillText
.
If we use the xPosition
and
yPosition
calculated using the width
and height of the canvas, the message
variable that contains the default or
user-input text, and the fill
OrStroke
variable to determine how to
render the text, we can display the text as configured by the user in
drawScreen()
:
var metrics = context.measureText(message); var textWidth = metrics.width; var xPosition = (theCanvas.width/2) - (textWidth/2); var yPosition = (theCanvas.height/2); switch(fillOrStroke) { case "fill": context.fillStyle = "#FF0000"; context.fillText (message, xPosition,yPosition); break; case "stroke": context.strokeStyle = "#FF0000"; context.strokeText (message, xPosition,yPosition); break; case "both": context.fillStyle = "#FF0000"; context.fillText (message, xPosition,yPosition); context.strokeStyle = "#000000"; context.strokeText (message, xPosition,yPosition); break; }
Example 3-1 shows the full code for Text
Arranger. Test it out to see how the user controls in HTML affect the
canvas. There are not many ways to change the text here, but you can see
the difference between fillText
and
strokeText
. In the next section, we
will update this application to configure and render the text in
multiple ways.
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH3EX1: Text Arranger Version 1.0</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript"> window.addEventListener("load", eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function eventWindowLoaded() { canvasApp(); } function canvasApp() { var message = "your text"; var fillOrStroke ="fill"; if (!canvasSupport()) { return; } var theCanvas = document.getElementById("canvasOne"); var context = theCanvas.getContext("2d"); var formElement = document.getElementById("textBox"); formElement.addEventListener("keyup", textBoxChanged, false); formElement = document.getElementById("fillOrStroke"); formElement.addEventListener("change", fillOrStrokeChanged, false); drawScreen(); function drawScreen() { //Background context.fillStyle = "#ffffaa"; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = "#000000"; context.strokeRect(5, 5, theCanvas.width−10, theCanvas.height−10); //Text context.font = "50px serif" var metrics = context.measureText(message); var textWidth = metrics.width; var xPosition = (theCanvas.width/2) - (textWidth/2); var yPosition = (theCanvas.height/2); switch(fillOrStroke) { case "fill": context.fillStyle = "#FF0000"; context.fillText (message, xPosition,yPosition); break; case "stroke": context.strokeStyle = "#FF0000"; context.strokeText (message, xPosition,yPosition); break; case "both": context.fillStyle = "#FF0000"; context.fillText (message, xPosition ,yPosition); context.strokeStyle = "#000000"; context.strokeText (message, xPosition,yPosition); break; } } function textBoxChanged(e) { var target = e.target; message = target.value; drawScreen(); } function fillOrStrokeChanged(e) { var target = e.target; fillOrStroke = target.value; drawScreen(); } } </script> </head> <body> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="300"> Your browser does not support HTML5 Canvas. </canvas> <form> Text: <input id="textBox" placeholder="your text" /> <br> Fill Or Stroke : <select id="fillOrStroke"> <option value="fill">fill</option> <option value="stroke">stroke</option> <option value="both">both</option> </select> <br> </form> </div> </body> </html>
Setting the Text Font
Now that we have placed text on the canvas, it’s time to explore
some of the basics of setting the context.font
property. As you will see,
specifying the font for displaying basic text on Canvas is really no
different from doing the same thing in HTML and CSS.
Font Size, Face Weight, and Style Basics
It is very easy to style text that will be rendered on the canvas.
It requires you to set the size, weight, style, and font face in a
CSS-compliant text string that is applied to the context.font
property. The basic format looks
like this:
[font style ] [font
weight ] [font size ]
[font face ] |
An example might be:
context.font = "italic bold 24px serif";
or:
context.font = "normal lighter 50px cursive";
Once the context.font
property
is set, it will apply to all text that is rendered
afterward—until the context.font
is set to another CSS-compliant
string.
Handling Font Size and Face in Text Arranger
In Text Arranger, we have implemented only a subset of the available font options for displaying text. We have chosen these to make the application work in as many browsers as possible. Here is a short rundown of the options we will implement.
Available font styles
CSS defines the valid font styles as:
normal | italic | oblique | inherit |
In Text Arranger, we have implemented all but inherit
.
Here is the markup we used to create the font style <select>
box in HTML. We made the id
of the form control equal to fontStyle
. We will use this id when we
listen for a change
event, which is
dispatched when the user updates the value of this control. We will do
this for all the controls in this version of Text Arranger:
<select id="fontStyle"> <option value="normal">normal</option> <option value="italic">italic</option> <option value="oblique">oblique</option> </select>
Available font weights
CSS defines the valid font weights as:
normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit | auto |
We have used only normal
,
bold
, bolder
, and lighter
in Text Arranger. You can add the
other values as you see fit.
Here is the markup we used to create the font weight <select>
box in HTML:
<select id="fontWeight"> <option value="normal">normal</option> <option value="bold">bold</option> <option value="bolder">bolder</option> <option value="lighter">lighter</option> </select>
Generic font faces
Because we cannot be sure which font will be available in the
browser at any time, we have limited the font face choices in Text
Arranger to those that are defined as “generic” in the CSS
specification: serif
, sans-serif
, cursive
, fantasy
, and monospace
.
Here is the markup we used to create the font face <select>
box in HTML:
<select id="textFont"> <option value="serif">serif</option> <option value="sans-serif">sans-serif</option> <option value="cursive">cursive</option> <option value="fantasy">fantasy</option> <option value="monospace">monospace</option> </select>
Font size and HTML5 range control
To specify the size of the font, we have implemented the new
HTML5 range
form control. range
is an <input>
type that creates a slider on
the HTML page to limit the numerical input to that specified in the
range. A range
is created by
specifying range
as the type
of a form input control. range
has four properties that can be
set:
min
The minimum value in the range
max
The maximum value in the range
step
The number of units to step when the range slider is moved
value
The default value of the range
Here is the markup we used to specify the range in the Text Arranger HTML:
<input type="range" id="textSize" min="0" max="200" step="1" value="50"/>
If the browser does not support this range
control, it will be rendered as a text
box.
Note
At the time of this writing, range
did not render in Firefox.
Creating the necessary variables in the canvasApp() function
In the canvasApp()
container
function, we need to create four variables—fontSize
, fontFace
, fontWeight
, and fontStyle
—that will hold the values set by
the HTML form controls for Text Arranger. We create a default value
for each so that the canvas can render text the first time the
drawScreen()
function is called.
After that, drawScreen()
will be
called only when a change
event is
handled by one of the event handler functions we will create for each
form control:
var fontSize = "50"; var fontFace = "serif"; var fontWeight = "normal"; var fontStyle = "normal";
Setting event handlers in canvasApp()
Just like we did in version 1.0 of Text Arranger, we need to
create event listeners and the associated event handler functions so
changes on the HTML page form controls can interact with HTML5 Canvas.
All of the event listeners below listen for a change
event on the form control:
formElement = document.getElementById("textSize"); formElement.addEventListener('change', textSizeChanged, false); formElement = document.getElementById("textFont"); formElement.addEventListener('change', textFontChanged, false); formElement = document.getElementById("fontWeight"); formElement.addEventListener('change', fontWeightChanged, false); formElement = document.getElementById("fontStyle"); formElement.addEventListener('change', fontStyleChanged, false);
Defining event handler functions in canvasApp()
Below are the event handlers we need to create for each form
control. Notice that each handler updates the variable associated with
part of the valid CSS font string, and then calls drawScreen()
so the new text can be painted
onto the canvas:
function textSizeChanged(e) { var target = e.target; fontSize = target.value; drawScreen(); } function textFontChanged(e) { var target = e.target; fontFace = target.value; drawScreen(); } function fontWeightChanged(e) { var target = e.target; fontWeight = target.value; drawScreen(); } function fontStyleChanged(e) { var target = e.target; fontStyle = target.value; drawScreen(); }
Font Color
Setting the font color for text rendered on HTML5 Canvas is as
simple as setting the context.fillStyle
or context.strokeStyle
property to a valid CSS
RGB color. Use the format
“#RRGGBB”, where RR is the red component hexadecimal value, GG is the
green component hexadecimal value, and BB is the blue component
hexadecimal value. Here are some examples:
context.fillStyle = "#FF0000";
Sets the text fill to red.
context.strokeStyle = "#FF00FF";
Sets the text stroke to purple.
context.fillStyle = "#FFFF00";
Sets the text fill to yellow.
Handling font color with JSColor
For Text Arranger, we will allow the user to select the text
color. We could have made this a drop-down or a text box, but instead,
we want to use the new HTML5 <input>
type of color
. This handy new form control works
directly in the web browser, allowing users to visually choose a color
from a beautifully designed color picker. At the time of this writing,
only Opera has implemented the color
<input>
object of the HTML5 specification.
However, since we could really use a nice color picker for Text
Arranger, we will implement a third-party color picker, JSColor
(http://jscolor.com/). The jsColor
control creates a nice color picker
in JavaScript (see Figure 3-5), similar
to the one that will someday grace browsers supporting HTML5.
To implement jsColor
and the
color picker for Text Arranger, first download the
jscolor.js library and put it in the same folder
as Text Arranger. Then, add this line of code in the <head>
to include jsColor
in the HTML page:
<script type="text/javascript" src="jscolor/jscolor.js"></script>
Then add a new <input>
element to the ever-growing HTML <form>
on the Text Arranger HTML page,
and give it the CSS class designation color
:
<input class="color" id="textFillColor" value="FF0000"/>
When you pick a color with jsColor
, it creates a text value that looks
like “FF0000”, representing the color value chosen. However, we
already know that we need to append the pound (#) sign to the front of
that value to work with HTML5 Canvas. The textFillColorChanged
event handler does
this by appending “#” to the value of the textFillColor
form control:
function textFillColorChanged(e) { var target = e.target; textFillColor = "#" + target.value; drawScreen(); }
Oh yes, and let’s not forget the event listener we must create
so that we can direct and “change” events
from the textFillColor
<input>
element to the textFillColor
Changed()
event
handler:
formElement = document.getElementById("textFillColor"); formElement.addEventListener('change', textFillColorChanged, false);
Finally, in the canvasApp()
function, we need to create the textFillColor
variable:
var textFillColor = "#ff0000";
We do this so that the variable can be updated by the
aforementioned event handler, and then implemented when that event
handler calls the drawScreen()
function:
switch(fillOrStroke) { case "fill": context.fillStyle = textFillColor; context.fillText (message, xPosition,yPosition); break; case "stroke": context.strokeStyle = textFillColor; context.strokeText (message, xPosition,yPosition); break; case "both": context.fillStyle = textFillColor; context.fillText (message, xPosition ,yPosition); context.strokeStyle = "#000000"; context.strokeText (message, xPosition,yPosition); break; }
Notice that we needed to update the switch()
statement created for Text Arranger
version 1.0 so that it used textFillColor
instead of hardcoded values.
However, when both a stroke and a fill are chosen, we still render the
stroke as black (“#000000”). We could have added an additional color
picker for the strokeColor
, but
that is something you can do if you want to start expanding the
application. Figure 3-5 illustrates
what it looks like now.
Font Baseline and Alignment
You have options to align text on HTML5 Canvas both vertically and horizontally. These alignments affect the text in relation to Canvas itself, but only to the invisible bounding box that would surround the text’s topmost, bottommost, rightmost, and leftmost sides. This is an important distinction because it means these alignments affect the text in ways that might be unfamiliar to you.
Vertical alignment
The font baseline is the vertical alignment of the font glyphs
based on predefined horizontal locations in a font’s em square
(the grid used to design font
outlines) in relation to font descenders. Basically, font glyphs, like
lowercase p and y that
traditionally extend “below the line,” have
descenders. The baseline tells the canvas where
to render the font based on how those descenders relate to other
glyphs in the font face.
The HTML5 Canvas API online has a neat graphic that attempts to explain baseline. We could copy it here, but in reality, we think it’s easier to understand by doing, which is one of the main reasons we wrote the Text Arranger application.
The options for the context.textBaseline
property are:
top
The top of the text
em square
and the top of the highest glyph in the font face. Selecting this baseline will push the text the farthest down (highesty
position) the canvas of all the baselines.hanging
This is a bit lower than the
top
baseline. It is the horizontal line from which many glyphs appear to “hang” from near the top of their face.middle
The dead vertical center baseline. We will use
middle
to help us vertically center the text in Text Arranger.alphabetic
The bottom of vertical writing script glyphs such as Arabic, Latin, and Hebrew.
ideographic
The bottom of horizontal writing script glyphs such as Han Ideographs, Katakana, Hiragana, and Hangul.
bottom
The bottom of the
em square
of the font glyphs. Choosing this baseline will push the font the farthest up (lowesty
position) the canvas.
So, for example, if you want to place your text with a top
baseline, you would use the following
code:
context.textBaseline = "top";
All text displayed on the canvas afterward would have this baseline. To change the baseline, you would change the property:
context.textBaseline = "middle";
In reality, you will probably choose a single baseline for your app and stick with it, unless you are creating a word-processing or design application that requires more precise text handling.
Horizontal alignment
The context.textAlign
property represents the horizontal alignment of the text based on its
x
position. These are the available
textAlign
values:
center
The dead horizontal center of the text. We can use this alignment to help center our text in Text Arranger.
start
Text is displayed directly after the text
y
position.end
All text is displayed before the text
y
position.left
Text is displayed starting with the
y
position of the text in the leftmost position (just likestart
).right
Text is displayed with the
y
position in the rightmost position of the text (just likeend
).
For example, to set the text alignment to center
, you would use the code:
context.textAlign = "center";
After this property is set, all text would be displayed with the
y
value of the text as the center
point. However, this does not mean the text will be “centered” on the
canvas. To do that, you need to find the center of the canvas, and use
that location as the y
value for
the text position. We will do this in Text Arranger.
These values can also be modified by the dir
attribute of the Canvas
object (inherited from the DOM
document
object). dir
changes the direction of how text is
displayed; the valid values for dir
are rtl
(“right to left”) and
ltr
(“left to right”).
Handling text baseline and alignment
We are going to handle the text baseline and alignment much like
we handled the other text properties in Text Arranger. First, we will
add some variables to the canvasApp()
function in which Text Arranger
operates that will hold the alignment values. Notice that we have set
the textAlign
variable to center
, helping us simplify centering the
text on the canvas:
var textBaseline = "middle"; var textAlign = "center";
Next, we add the <select>
form elements for each new
attribute to the HTML portion of the page:
Text Baseline <select id="textBaseline"> <option value="middle">middle</option> <option value="top">top</option> <option value="hanging">hanging</option> <option value="alphabetic">alphabetic</option> <option value="ideographic">ideographic</option> <option value="bottom">bottom</option> </select> <br> Text Align <select id="textAlign"> <option value="center">center</option> <option value="start">start</option> <option value="end">end</option> <option value="left">left</option> <option value="right">right</option> </select>
We then add event listeners and event handler functions so we
can connect the user interaction with the HTML form elements to the
canvas display. We register the event listeners in the canvasApp()
function:
formElement = document.getElementById("textBaseline"); formElement.addEventListener('change', textBaselineChanged, false); formElement = document.getElementById("textAlign"); formElement.addEventListener('change', textAlignChanged, false);
Next, we need to create the event handler functions inside
canvasApp()
:
function textBaselineChanged(e) { var target = e.target; textBaseline = target.value; drawScreen(); } function textAlignChanged(e) { var target = e.target; textAlign = target.value; drawScreen(); }
We then apply the new values in the drawScreen()
function:
context.textBaseline = textBaseline; context.textAlign = textAlign;
Finally, we change the code that centers the text horizontally
on the screen. Because we used the center
alignment for context.textAlign
, we no longer need to
subtract half the width of the text that we retrieved through context.measureText()
like we did previously
in Text Arranger 1.0:
var metrics = context.measureText(message); var textWidth = metrics.width; var xPosition = (theCanvas.width/2) - (textWidth/2);
Instead, we can simply use the center point of the canvas:
var xPosition = (theCanvas.width/2);
Remember, center
is only the
default alignment for the text. Because you can change this with Text
Arranger, the text can still be aligned in different ways while you
are using the application.
Figure 3-6
shows how a font set to start
alignment with a middle
baseline
might appear on the canvas.
Text Arranger Version 2.0
Now, try the new version of Text Arranger, shown in Example 3-2. You can see that we have added a ton of new options that did not exist in version 1.0. One of the most striking things is how fluidly the text grows and shrinks as the font size is updated. Now, imagine scripting the font size to create animations. How would you do that? Could you create an application to record the manipulations the user makes with Text Arranger, and then play them back in real time?
Also, notice how all the alignment options affect one another. Experiment with how changing the text direction affects the vertical alignment. Choose different font faces and see how they affect the baseline. Do you see how an application like Text Arranger can help you understand the complex relationships of all the text properties on HTML5 Canvas in an interactive and—dare we say—fun way?
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH3EX2: Text Arranger Version 2.0</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript" src="jscolor/jscolor.js"></script> <script type="text/javascript"> window.addEventListener("load", eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function eventWindowLoaded() { canvasApp(); } function canvasApp() { var message = "your text"; var fillOrStroke = "fill"; var fontSize = "50"; var fontFace = "serif"; var textFillColor = "#ff0000"; var textBaseline = "middle"; var textAlign = "center"; var fontWeight = "normal"; var fontStyle = "normal"; if (!canvasSupport()) { return; } var theCanvas = document.getElementById("canvasOne"); var context = theCanvas.getContext("2d"); var formElement = document.getElementById("textBox"); formElement.addEventListener('keyup', textBoxChanged, false); formElement = document.getElementById("fillOrStroke"); formElement.addEventListener('change', fillOrStrokeChanged, false); formElement = document.getElementById("textSize"); formElement.addEventListener('change', textSizeChanged, false); formElement = document.getElementById("textFillColor"); formElement.addEventListener('change', textFillColorChanged, false); formElement = document.getElementById("textFont"); formElement.addEventListener('change', textFontChanged, false); formElement = document.getElementById("textBaseline"); formElement.addEventListener('change', textBaselineChanged, false); formElement = document.getElementById("textAlign"); formElement.addEventListener('change', textAlignChanged, false); formElement = document.getElementById("fontWeight"); formElement.addEventListener('change', fontWeightChanged, false); formElement = document.getElementById("fontStyle"); formElement.addEventListener('change', fontStyleChanged, false); drawScreen(); function drawScreen() { //Background context.fillStyle = "#ffffaa"; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = "#000000"; context.strokeRect(5, 5, theCanvas.width−10, theCanvas.height−10); //Text context.textBaseline = textBaseline; context.textAlign = textAlign; context.font = fontWeight + " " + fontStyle + " " + fontSize + "px " + fontFace; var xPosition = (theCanvas.width/2); var yPosition = (theCanvas.height/2); switch(fillOrStroke) { case "fill": context.fillStyle = textFillColor; context.fillText (message, xPosition,yPosition); break; case "stroke": context.strokeStyle = textFillColor; context.strokeText (message, xPosition,yPosition); break; case "both": context.fillStyle = textFillColor; context.fillText (message, xPosition ,yPosition); context.strokeStyle = "#000000"; context.strokeText (message, xPosition,yPosition); break; } } function textBoxChanged(e) { var target = e.target; message = target.value; drawScreen(); } function fillOrStrokeChanged(e) { var target = e.target; fillOrStroke = target.value; drawScreen(); } function textSizeChanged(e) { var target = e.target; fontSize = target.value; drawScreen(); } function textFillColorChanged(e) { var target = e.target; textFillColor = "#" + target.value; drawScreen(); } function textFontChanged(e) { var target = e.target; fontFace = target.value; drawScreen(); } function textBaselineChanged(e) { var target = e.target; textBaseline = target.value; drawScreen(); } function textAlignChanged(e) { var target = e.target; textAlign = target.value; drawScreen(); } function fontWeightChanged(e) { var target = e.target; fontWeight = target.value; drawScreen(); } function fontStyleChanged(e) { var target = e.target; fontStyle = target.value; drawScreen(); } } </script> </head> <body> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="300"> Your browser does not support HTML5 Canvas. </canvas> <form> Text: <input id="textBox" placeholder="your text" /> <br> Fill Or Stroke : <select id="fillOrStroke"> <option value="fill">fill</option> <option value="stroke">stroke</option> <option value="both">both</option> </select> <br> Text Font: <select id="textFont"> <option value="serif">serif</option> <option value="sans-serif">sans-serif</option> <option value="cursive">cursive</option> <option value="fantasy">fantasy</option> <option value="monospace">monospace</option> </select> <br> Text Size: <input type="range" id="textSize" min="0" max="200" step="1" value="50"/> <br> Text Color: <input class="color" id="textFillColor" value="FF0000"/> <br> Font Weight: <select id="fontWeight"> <option value="normal">normal</option> <option value="bold">bold</option> <option value="bolder">bolder</option> <option value="lighter">lighter</option> </select> <br> Font Style: <select id="fontStyle"> <option value="normal">normal</option> <option value="italic">italic</option> <option value="oblique">oblique</option> </select> <br> Text Baseline <select id="textBaseline"> <option value="middle">middle</option> <option value="top">top</option> <option value="hanging">hanging</option> <option value="alphabetic">alphabetic</option> <option value="ideographic">ideographic</option> <option value="bottom">bottom</option> </select> <br> Text Align <select id="textAlign"> <option value="center">center</option> <option value="start">start</option> <option value="end">end</option> <option value="left">left</option> <option value="right">right</option> </select> </div> </body> </html>
Text and the Canvas Context
We’ve already discussed a couple Canvas context properties that
affect the canvas in a global fashion: fillStyle
and strokeStyle
. However, there are two areas that
visually demonstrate how changes to the properties of the context can
affect the entire HTML5 Canvas: alpha transparencies and
shadows.
Global Alpha and Text
Using alpha is a cool way to make objects seem to be partially or
fully transparent on HTML5 Canvas. The globalAlpha
property of the Canvas context is
used for this purpose. After globalAlpha
is applied, it affects all drawing
on the canvas, so you need to be careful when setting it.
The valid values for context.globalAlpha
are numbers between 0.0
(transparent) and 1.0 (opaque), and they act as a percentage for the
alpha value. For example, a 50% alpha value would be coded like
this:
context.globalAlpha = 0.5;
A 100% alpha (no transparency) would be coded like this:
context.globalAlpha = 1.0;
Handling globalAlpha transparencies
Besides the now-familiar elements that we included for most of
the other configurable options in Text Arranger, the globalAlpha
property requires us to think a
bit more about when we use it and how it will affect the rest of the
canvas.
First, we create a variable named textAlpha
in the canvasApp()
function and initialize it with
1
, which means the text will have
no transparency when it is first displayed:
var textAlpha = 1;
Next, in the drawImage()
function, we need to set the globalAlpha
property twice: once before we
draw the background and the bounding box frame…
function drawScreen() { //Background context.globalAlpha = 1;
…and then again to the value stored in textAlpha
, just before rendering the text to
the canvas:
context.globalAlpha = textAlpha;
This will reset globalAlpha
so we can draw the background, but it will still allow us to use a
configurable alpha value for the displayed text.
We will use another HTML5 range
control in our form, but this time we
set the value range with a min of 0.0 and a max of 1.0, stepping 0.01
every time the range is moved:
Alpha: <input type="range" id="textAlpha" min="0.0" max="1.0" step="0.01" value="1.0"/>
The textAlphaChanged()
function works just like the other event handler functions we created
in this chapter:
function textAlphaChanged(e) { var target = e.target; textAlpha = (target.value); drawScreen(); }
Also, don’t forget the event listener for the textAlpha range
control:
formElement = document.getElementById("textAlpha"); formElement.addEventListener('change', textAlphaChanged, false);
The results will look like Figure 3-7.
Global Shadows and Text
HTML5 Canvas includes a unique set of properties for creating a
shadow for drawings. The context.shadow
functions are not unique to
text, but they can make some very good text effects with very little
effort.
To create a shadowEffect
, there
are four properties of the Canvas context that need to be
manipulated:
context.shadowColor
The color of the shadow. This uses the same “#RRGGBB” format of the
fillStyle
andstrokeStyle
properties.context.shadowOffsetX
The
x
offset of shadow. This can be a positive or negative number.context.shadowOffsetY
The
y
offset of shadow. This can be a positive or negative number.context.shadowBlur
The blur filter diffusion of the shadow. The higher the number, the more diffusion.
For example, if you want to create a red shadow that is 5 pixels to the right and 5 pixels down from your text, with a blur of 2 pixels, you would set the properties like this:
context.shadowColor = "#FF0000"; context.shadowOffsetX = 5; context.shadowOffsetY = 5; context.shadowBlur = 2;
Handling global shadows
Just like we saw with globalAlpha
, we must reset the shadow
properties before we draw the background for textArranger
; otherwise, the shadow will
apply to the entire image. First, in the canvasApp()
function, we create a set of
variables to hold the shadow values:
var textAlpha = 1; var shadowX = 1; var shadowY = 1; var shadowBlur = 1; var shadowColor = "#707070";
We then make sure to turn off the shadow before we render the
background for textArranger
in the drawScreen()
. We don’t have to reset the
shadowColor
, but we think it is
good practice to update all the relative properties relating to any
global change to the Canvas context:
context.shadowColor = "#707070"; context.shadowOffsetX = 0; context.shadowOffsetY = 0; context.shadowBlur = 0;
Later in drawScreen()
, we
render the shadow based on the settings in the four variables we
created:
context.shadowColor = shadowColor; context.shadowOffsetX = shadowX; context.shadowOffsetY = shadowY; context.shadowBlur = shadowBlur;
We also need to create the HTML to allow the user to update the
shadow settings. We do this with three range
controls, as well as another color
picker using jsColor
:
Shadow X:<input type="range" id="shadowX" min="−100" max="100" step="1" value="1"/> <br> Shadow Y:<input type="range" id="shadowY" min="−100" max="100" step="1" value="1"/> <br> Shadow Blur: <input type="range" id="shadowBlur" min="1" max="100" step="1" value="1" /> <br> Shadow Color: <input class="color" id="shadowColor" value="707070"/>
Finally, we need to add the event listeners and event handler functions so the HTML form elements can communicate with the canvas. See the results in Figure 3-8:
formElement = document.getElementById("shadowX"); formElement.addEventListener('change', shadowXChanged, false); formElement = document.getElementById("shadowY"); formElement.addEventListener('change', shadowYChanged, false); formElement = document.getElementById("shadowBlur"); formElement.addEventListener('change', shadowBlurChanged, false); formElement = document.getElementById("shadowColor"); formElement.addEventListener('change', shadowColorChanged, false); function shadowXChanged(e) { var target = e.target; shadowX = target.value; drawScreen(); } function shadowYChanged(e) { var target = e.target; shadowY = target.value; drawScreen(); } function shadowBlurChanged(e) { var target = e.target; shadowBlur = target.value; drawScreen(); } function shadowColorChanged(e) { var target = e.target; shadowColor = target.value; drawScreen(); }
Text with Gradients and Patterns
We’ve already explored the fillColor
and strokeColor
properties of the Canvas context by
setting those value to CSS-compliant colors. However, those very same
properties can be set to refer to a few other objects defined in the
Canvas API to create some stunning text effects. The objects
are:
- Linear gradient
A linear color gradient with two or more colors
- Radial gradient
A circular color gradient with two or more colors
- Image pattern
An
Image
object used as a fill pattern
Linear Gradients and Text
To create a linear gradient, make a call to the context’s createLinearGradient()
method to create a
Gradient
object. The createLinearGradient()
method accepts four
parameters that all define the line of the linear gradient. The
x0
and
y0
parameters
are the starting point of the line, and x1
and y1
represent the ending point of
the line:
var gradient = context.createLinearGradient([x0]
,[y0]
,[x1]
,[y1]
);
For example, if you want to create a linear gradient that starts at the beginning of the text (located at 100,100), and has an endpoint that is the width of your text as displayed on the canvas, you might write the following code:
var metrics = context.measureText(message); var textWidth = metrics.width; var gradient = context.createLinearGradient(100, 100, textWidth, 100);
After you have created the line that represents the gradient, you
need to add colors that will form the gradations of the gradient fill.
This is done with the addColorStop()
method, which requires two arguments: offset
and color
:
gradient.addColorStop([offset],[color]);
offset
This is the offset on the gradient line to start the color gradation. The entire gradient is represented by the numbers between 0.0 and 1.0. The offset will be a decimal that represents a percentage.
color
A valid CSS color in the format “#RRGGBB”.
So, if you want black to be the first color in the gradient, and
then red to be the second color that starts
halfway down the gradient line, you would create two calls to add
ColorStop()
:
gradient.addColorStop(0, "#000000"); gradient.addColorStop(.5, "#FF0000");
Note
If you fail to add colors with addColorStop()
, the text will be rendered
invisible.
The results are shown in Figure 3-9.
Radial Gradients and Text
A radial gradient is created much like a linear gradient, except
that it represents a cone—not a
line. The cone is created by defining the center points and the radii of
two different circles when calling the createRadialGradient()
function of the Canvas
context:
var gradient = context.createRadialGradient([x0]
,[y0]
,[radius0]
,[x1]
,[y1]
,[radius1]
);
Let’s say you want to create a radial gradient based on a cone. It starts with a circle that has its center point at 100,100 and a radius of 20, and ends at a circle with its center point at 200,100 and a radius of 5. The code would look like this:
var gradient = context.createRadialGradient(100,100,20,200,100,5);
Adding color stops to a radial gradient works the same as with a linear gradient, except the color moves along the cone instead of the line:
gradient.addColorStop(0, "#000000 "); gradient.addColorStop(.5, "#FF0000");
Image Patterns and Text
Another option for filling text on HTML5 Canvas is to use an
Image
object. We will devote all of
Chapter 4 to using the Image API, so here
we will only discuss the basics of how to use one as a pattern for a
text fill.
To create an image pattern, call the createPattern()
method of the Canvas context,
passing a reference to an Image
object, and an option for repetition
:
var pattern = context.createPattern([image]
,[repetition]
);
image
A valid
Image
object that has been loaded with an image by setting thepattern
.src
property and waiting for the image to load by setting an event listener for theImage onload
event. The Canvas specification also allows for avideo
element or another<canvas>
to be used here as well.repetition
The “tiling” of the image. This can have one of four values:
repeat
The image is tiled on both the x and y axes.
repeat-x
The image is tiled only on the x-axis (horizontally).
repeat-y
The image is tiled only on the y-axis (vertically).
no-repeat
The image is not tiled.
To use the image pattern, apply it to the fillColor
and strokeColor
properties of the context, just as
you would apply a color:
context.fillStyle = pattern;
or:
context.strokeStyle = pattern;
For example, to load an image named texture.jpg and apply it to the fillStyle
so that it tiles on both the x and y
axes, you would write code like this:
var pattern = new Image(); pattern.src = "texture.jpg" pattern.onload = function() { var pattern = context.createPattern("texture.jpg", "repeat"); context.fillStyle = pattern; ... }
Handling Gradients and Patterns in Text Arranger
Text Arranger 3.0 includes many changes that were implemented to
support using gradients and image patterns with text on HTML5 Canvas. To
see these changes in action, we first need to make sure that we have
preloaded the texture.jpg image,
which we will use for the context.createPattern()
functionality. To do
this, we will create a new function named eventAssetsLoaded()
that we will set as the
event handler for the onload
event of
the Image
object that will hold the
pattern. When that image has loaded, we will call canvasApp()
in the same way we called it from
eventWindowLoaded()
:
function eventWindowLoaded() { var pattern = new Image(); pattern.src = "texture.jpg"; pattern.onload = eventAssetsLoaded; } function eventAssetsLoaded() { canvasApp(); }
Note
We are not going to use the pattern variable we created in this
function, as it does not have scope in the canvasApp()
function. We are merely using it
to make sure that the image is available before we use it.
In the canvasApp()
function, we
will create three variables to support this new functionality. fillType
describes how the text will be filled
(a regular color fill, a linear gradient, a radial gradient, or a
pattern). The textColorFill2
variable
is the second color we will use for the gradient color stop. Finally,
the pattern
variable holds the
Image
object we preloaded, which we
now need to create an instance of in canvasApp()
:
var fillType = "colorFill"; var textFillColor2 = "#000000"; var pattern = new Image(); ... pattern.src = "texture.jpg";
Now, let’s jump to the HTML of our <form>
. Since we have created different
ways to fill the text we are displaying, we need to build a selection
that allows for this choice. We will create a <select>
box with the id of fillType
for this purpose:
Fill Type: <select id="fillType"> <option value="colorFill">Color Fill</option> <option value="linearGradient">Linear Gradient</option> <option value="radialGradient">Radial Gradient</option> <option value="pattern">pattern</option> </select>
We need to add a second color selection that we can use for the
gradient fills. We will use the jsColor
picker and the id textColorFill2
:
Text Color 2: <input class="color" id="textFillColor2" value ="000000"/> <br>
Back in canvasApp()
, we need to
create the event listeners for our two new form elements:
formElement = document.getElementById("textFillColor2"); formElement.addEventListener('change', textFillColor2Changed, false); formElement = document.getElementById("fillType"); formElement.addEventListener('change', fillTypeChanged, false);
We also need to create the associated event handler functions for the new form elements:
function textFillColor2Changed(e) { var target = e.target; textFillColor2 = "#" + target.value; drawScreen(); } function fillTypeChanged(e) { var target = e.target; fillType = target.value; drawScreen(); }
We need to add support to drawScreen()
for this new functionality.
First, we use the measureText()
method of the context to get the width of the text, which we will use to
create the gradients:
var metrics = context.measureText(message); var textWidth = metrics.width;
Then, we need to decide how to format our “color” for the fillStyle
or strokeStyle
of the context. In this instance,
it can be a CSS color, a gradient, or an image pattern; the list below
provides more information.
- Color fill
If we are doing a simple color fill, we operate just like in previous versions of Text Arranger. All we need to do is make
tempColor
equal to the value ofy
.- Linear gradient
For the linear gradient, we need to decide what line we are going to create for the gradient. Our line will start at the beginning of the text (
xPosition
-
textWidth/2
because the text uses thecenter
alignment), and runs horizontally to the end of the text (textWidth
). We also add two color stops (at 0 and 60%)—the colors aretextFillColor1
andtextFillColor2
.- Radial gradient
For the radial gradient, we are going to create a cone that starts at the center of the text (
xPosition
,yPosition
) with a radius the size of the font (fontSize
). The cone will extend horizontally the width of the text (textWidth
) with a radius of 1.- Pattern
For this option, we create a pattern using the pattern
image
variable we previously created. We designate it torepeat
so it will tile horizontally and vertically.
Here’s the code:
var tempColor; if (fillType == "colorFill") { tempColor = textFillColor; } else if (fillType == "linearGradient") { var gradient = context.createLinearGradient(xPosition- textWidth/2, yPosition, textWidth, yPosition); gradient.addColorStop(0,textFillColor); gradient.addColorStop(.6,textFillColor2); tempColor = gradient; } else if (fillType == "radialGradient") { var gradient = context.createRadialGradient(xPosition, yPosition, fontSize, xPosition+textWidth, yPosition, 1); gradient.addColorStop(0,textFillColor); gradient.addColorStop(.6,textFillColor2); tempColor = gradient; } else if (fillType == "pattern") { var tempColor = context.createPattern(pattern,"repeat"); } else { tempColor = textFillColor; }
Now, when we set our fillStyle
or strokeStyle
, we use tempColor
instead of textFillColor
. This will set the proper text
fill choice that will be displayed on the canvas, as shown in Figure 3-10:
context.fillStyle = tempColor;
Width, Height, Scale, and toDataURL() Revisited
In Chapter 1, we briefly
discussed that you can set the width and height of the canvas, as well as
the scale (style width
and height
) of the canvas display area, dynamically
in code. We also showed you an example of using the Canvas
object’s toDataURL()
method to export a “screenshot” of
the Canvas application. In this section, we will revisit those functions
as they relate to Text Arranger 3.0.
Dynamically Resizing the Canvas
In the code we developed in this chapter, we created a reference
to the Canvas
object on the HTML
page—with the id canvasOne
—and used
it to retrieve the 2D context of the Canvas
object:
var theCanvas = document.getElementById("canvasOne"); var context = theCanvas.getContext("2d");
While the 2D context is very important because we used it to draw
directly onto the canvas, we did not spend any time discussing the
Canvas
object itself. In this
chapter, we use the width
property of
the Canvas
object to center text on
the canvas. However, the Canvas
object also includes another property named height
, and both of these properties can be
used to dynamically resize the Canvas
object on demand. Why would you want to do this? There could be many
uses, such as:
Updating the canvas to the exact size of a loaded
video
objectDynamically animating the canvas after the page is loaded
Other, more creative uses like the one we will experiment with next
Resizing the canvas on the fly is quite easy. To do it, simply set
the width
and height
properties of the Canvas
object, and then redraw the canvas
contents:
Canvas.width = 600; Canvas.height = 500; drawScreen();
The Canvas 2D API describes this function as a way to “scale” the
canvas, but in practice, this does not appear to be true. Instead, the
contents of the canvas are simply redrawn at the same size and same
location on a larger canvas. Furthermore, if you don’t redraw the canvas
content, it appears to be invalidated, blanking the canvas back to
white. To properly scale the canvas, you need to use the CSS width
and height
attributes, as described in the next
section. We discuss using a matrix transformation to scale the Canvas in
both Chapters 2 and 4.
Dynamically resizing in Text Arranger
We will add the ability for the canvas to be resized at will, giving you a good example of how resizing works and what it does to your drawn content.
First, we will add a couple new range
controls to the HTML <form>
. As you might have already
guessed, we really like this new HTML5 range
control, so we’ve tried to find as
many uses as possible for it—even though it’s only tangentially
related to HTML5 Canvas.
We will give the controls the ids canvasWidth
and canvasHeight
:
Canvas Width: <input type="range" id="canvasWidth" min="0" max="1000" step="1" value="500"/> <br> Canvas Height: <input type="range" id="canvasHeight" min="0" max="1000" step="1" value="300"/> <br>
Next, we add event listeners for the new form elements in the
canvasApp()
function:
formElement = document.getElementById("canvasWidth"); formElement.addEventListener('change', canvasWidthChanged, false); formElement = document.getElementById("canvasHeight"); formElement.addEventListener('change', canvasHeightChanged, false);
Finally, we add the event handlers.
Notice that we set the width
and
height
of the
Canvas
(the variable we created that
represents the Canvas
object on
screen) right inside these functions. We also need to make sure that
we call drawScreen()
in each
function so that the canvas is redrawn on the newly resized area. If
we did not do this, the canvas on the page would blank back to
white:
function canvasWidthChanged(e) { var target = e.target; theCanvas.width = target.value; drawScreen(); } function canvasHeightChanged(e) { var target = e.target; theCanvas.height = target.value; drawScreen(); }
We also need to change the way we draw the background for the
application in the drawScreen()
function so it supports a resized canvas. We do this by using the
width
and height
attributes of theCanvas
to create our background and
bounding box:
context.fillStyle = '#ffffaa'; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = '#000000'; context.strokeRect(5, 5, theCanvas.width−10, theCanvas.height−10);
Dynamically Scaling the Canvas
Besides resizing the canvas using theCanvas.width
and theCanvas.height
attributes, you can also use
CSS styles to change its scale. Unlike resizing, scaling takes the
current canvas bitmapped area and resamples it to fit into the size
specified by the width
and height
attributes of the CSS style. For
example, to scale the canvas to a 400×400 area, you might use this CSS
style:
style = "width: 400px; height:400px"
To update the style.width
and
style.height
properties of the canvas
in Text Arranger, we first create two more range
controls in the HTML page:
Canvas Style Width: <input type="range" id="canvasStyleWidth" min="0" max="1000" step="1" value="500"/> <br> Canvas Style Height: <input type="range" id="canvasStyleHeight" min="0" max="1000" step="1" value="300"/> <br>
Next, we set the event handler for each range
control. However, this time we are using
the same handler —canvasStyleSizeChanged()
—for both:
formElement = document.getElementById("canvasStyleWidth"); formElement.addEventListener("change", canvasStyleSizeChanged, false); formElement = document.getElementById("canvasStyleHeight"); formElement.addEventListener("change", canvasStyleSizeChanged, false);
In the event handler, we use the document.getElementById()
method to get the
values from both range
controls. We
then create a string that represents the style we want to set for the
canvas:
"width:" + styleWidth.value + "px; height:" + styleHeight.value +"px;";
Finally, we use the setAttribute()
method to set the
“style”:
function canvasStyleSizeChanged(e) { var styleWidth = document.getElementById("canvasStyleWidth"); var styleHeight = document.getElementById("canvasStyleHeight"); var styleValue = "width:" + styleWidth.value + "px; height:" + styleHeight.value +"px;"; theCanvas.setAttribute("style", styleValue ); drawScreen(); }
Note
While trying to change theCanvas.width
and theCanvas.height
attributes, you might
notice some oddities if you try to change the scale with CSS at the
same time. It appears that once you change the scale with CSS, the
width
and height
attributes update the canvas in
relation to that scale, which might not be the effect you are
expecting. Experiment with Text Arranger 3.0 to see how these
different styles and attributes interact.
The toDataURL() Method of the Canvas Object
As we briefly explained in Chapter 1, the Canvas
object also contains a method named
toDataURL()
, which returns a string
representing the canvas’ image data. A call with no arguments will
return a string of image data of MIME type image/png. If you supply the image/jpg as an argument, you can also supply
a second argument between the numbers 0.0 and 1.0 that represents the
quality/compression level of the image.
We are going to use toDataURL()
to output the image data of the canvas into a <textarea>
on our form, and then
open a window to display the actual image. This is just a simple way to
show that the function is working.
The first thing we do is create our last two form controls in HTML
for Text Arranger. We start by creating a button with the id of createImageData
that, when pressed, will
create the image data with a call to an event handler named createImageDataPressed()
.
We also create a <textarea>
named imageDataDisplay
that will hold the text data
of the image after the createImageData
button is pressed:
<input type="button" id="createImageData" value="Create Image Data"> <br> <br> <textarea id="imageDataDisplay" rows=10 cols=30></textarea>
Next, we set up the event listener for the createImageData
button:
formElement = document.getElementById("createImageData"); formElement.addEventListener('click', createImageDataPressed, false);
Then, in the createImageDataPressed()
event handler, we
call the toDataURL()
method of the
Canvas
object (theCanvas
), and set the value of the imageDataDisplay
<textarea>
to the data returned
from toDataURL()
. Finally, using the
image data as the URL for the window, we call window.open()
. When we do this, a window will
pop open, displaying the actual image created from the canvas (see Figure 3-11). You can
right-click and save this image, just like any other image displayed in
an HTML page. Pretty cool, eh?
function createImageDataPressed(e) { var imageDataDisplay = document.getElementById('imageDataDisplay'); imageDataDisplay.value = theCanvas.toDataURL(); window.open(imageDataDisplay.value,"canvasImage","left=0,top=0,width=" + theCanvas.width + ",height=" + theCanvas.height + ",toolbar=0,resizable=0"); }
Final Version of Text Arranger
The final version of Text Arranger (3.0) brings together all the HTML5 Text API features we have discussed in this chapter (see Example 3-3). Play with the final app, and see how the different options interact with one another. Here are a couple things you might find interesting:
Shadows don’t work with patterns or gradients.
Increasing the text size with a pattern that is the size of the canvas changes the pattern on the text (it acts like a mask or window into the pattern itself).
Canvas width and height are affected by the style width and height (scaling).
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH3EX3: Text Arranger 3.0</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript" src="jscolor/jscolor.js"></script> <script type="text/javascript"> window.addEventListener("load", eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function eventWindowLoaded() { var pattern = new Image(); pattern.src = "texture.jpg"; pattern.onload = eventAssetsLoaded; } function eventAssetsLoaded() { canvasApp(); } function canvasApp() { var message = "your text"; var fontSize = "50"; var fontFace = "serif"; var textFillColor = "#ff0000"; var textAlpha = 1; var shadowX = 1; var shadowY = 1; var shadowBlur = 1; var shadowColor = "#707070"; var textBaseline = "middle"; var textAlign = "center"; var fillOrStroke ="fill"; var fontWeight = "normal"; var fontStyle = "normal"; var fillType = "colorFill"; var textFillColor2 = "#000000"; var pattern = new Image(); if (!canvasSupport()) { return; } var theCanvas = document.getElementById("canvasOne"); var context = theCanvas.getContext("2d"); var formElement = document.getElementById("textBox"); formElement.addEventListener("keyup", textBoxChanged, false); formElement = document.getElementById("fillOrStroke"); formElement.addEventListener("change", fillOrStrokeChanged, false); formElement = document.getElementById("textSize"); formElement.addEventListener("change", textSizeChanged, false); formElement = document.getElementById("textFillColor"); formElement.addEventListener("change", textFillColorChanged, false); formElement = document.getElementById("textFont"); formElement.addEventListener("change", textFontChanged, false); formElement = document.getElementById("textBaseline"); formElement.addEventListener("change", textBaselineChanged, false); formElement = document.getElementById("textAlign"); formElement.addEventListener("change", textAlignChanged, false); formElement = document.getElementById("fontWeight"); formElement.addEventListener("change", fontWeightChanged, false); formElement = document.getElementById("fontStyle"); formElement.addEventListener("change", fontStyleChanged, false); formElement = document.getElementById("shadowX"); formElement.addEventListener("change", shadowXChanged, false); formElement = document.getElementById("shadowY"); formElement.addEventListener("change", shadowYChanged, false); formElement = document.getElementById("shadowBlur"); formElement.addEventListener("change", shadowBlurChanged, false); formElement = document.getElementById("shadowColor"); formElement.addEventListener("change", shadowColorChanged, false); formElement = document.getElementById("textAlpha"); formElement.addEventListener("change", textAlphaChanged, false); formElement = document.getElementById("textFillColor2"); formElement.addEventListener("change", textFillColor2Changed, false); formElement = document.getElementById("fillType"); formElement.addEventListener("change", fillTypeChanged, false); formElement = document.getElementById("canvasWidth"); formElement.addEventListener("change", canvasWidthChanged, false); formElement = document.getElementById("canvasHeight"); formElement.addEventListener("change", canvasHeightChanged, false); formElement = document.getElementById("canvasStyleWidth"); formElement.addEventListener("change", canvasStyleSizeChanged, false); formElement = document.getElementById("canvasStyleHeight"); formElement.addEventListener("change", canvasStyleSizeChanged, false); formElement = document.getElementById("createImageData"); formElement.addEventListener("click", createImageDataPressed, false); pattern.src = "texture.jpg"; drawScreen(); function drawScreen() { //Background context.globalAlpha = 1; context.shadowColor = "#707070"; context.shadowOffsetX = 0; context.shadowOffsetY = 0; context.shadowBlur = 0; context.fillStyle = "#ffffaa"; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = "#000000"; context.strokeRect(5, 5, theCanvas.width-10, theCanvas.height-10); //Text context.textBaseline = textBaseline; context.textAlign = textAlign; context.font = fontWeight + " " + fontStyle + " " + fontSize + "px " + fontFace; context.shadowColor = shadowColor; context.shadowOffsetX = shadowX; context.shadowOffsetY = shadowY; context.shadowBlur = shadowBlur; context.globalAlpha = textAlpha; var xPosition = (theCanvas.width/2); var yPosition = (theCanvas.height/2); var metrics = context.measureText(message); var textWidth = metrics.width; var tempColor; if (fillType == "colorFill") { tempColor = textFillColor; } else if (fillType == "linearGradient") { var gradient = context.createLinearGradient(xPosition- textWidth/2, yPosition, textWidth, yPosition); gradient.addColorStop(0,textFillColor); gradient.addColorStop(.6,textFillColor2); tempColor = gradient; } else if (fillType == "radialGradient") { var gradient = context.createRadialGradient(xPosition, yPosition, fontSize, xPosition+textWidth, yPosition, 1); gradient.addColorStop(0,textFillColor); gradient.addColorStop(.6,textFillColor2); tempColor = gradient; } else if (fillType == "pattern") { var tempColor = context.createPattern(pattern,"repeat") } else { tempColor = textFillColor; } switch(fillOrStroke) { case "fill": context.fillStyle = tempColor; context.fillText (message, xPosition,yPosition); break; case "stroke": context.strokeStyle = tempColor; context.strokeText (message, xPosition,yPosition); break; case "both": context.fillStyle = tempColor; context.fillText (message, xPosition,yPosition); context.strokeStyle = "#000000"; context.strokeText (message, xPosition,yPosition); break; } } function textBoxChanged(e) { var target = e.target; message = target.value; drawScreen(); } function textBaselineChanged(e) { var target = e.target; textBaseline = target.value; drawScreen(); } function textAlignChanged(e) { var target = e.target; textAlign = target.value; drawScreen(); } function fillOrStrokeChanged(e) { var target = e.target; fillOrStroke = target.value; drawScreen(); } function textSizeChanged(e) { var target = e.target; fontSize = target.value; drawScreen(); } function textFillColorChanged(e) { var target = e.target; textFillColor = "#" + target.value; drawScreen(); } function textFontChanged(e) { var target = e.target; fontFace = target.value; drawScreen(); } function fontWeightChanged(e) { var target = e.target; fontWeight = target.value; drawScreen(); } function fontStyleChanged(e) { var target = e.target; fontStyle = target.value; drawScreen(); } function shadowXChanged(e) { var target = e.target; shadowX = target.value; drawScreen(); } function shadowYChanged(e) { var target = e.target; shadowY = target.value; drawScreen(); } function shadowBlurChanged(e) { var target = e.target; shadowBlur = target.value; drawScreen(); } function shadowColorChanged(e) { var target = e.target; shadowColor = target.value; drawScreen(); } function textAlphaChanged(e) { var target = e.target; textAlpha = (target.value); drawScreen(); } function textFillColor2Changed(e) { var target = e.target; textFillColor2 = "#" + target.value; drawScreen(); } function fillTypeChanged(e) { var target = e.target; fillType = target.value; drawScreen(); } function canvasWidthChanged(e) { var target = e.target; theCanvas.width = target.value; drawScreen(); } function canvasHeightChanged(e) { var target = e.target; theCanvas.height = target.value; drawScreen(); } function canvasStyleSizeChanged(e) { var styleWidth = document.getElementById("canvasStyleWidth"); var styleHeight = document.getElementById("canvasStyleHeight"); var styleValue = "width:" + styleWidth.value + "px; height:" + styleHeight.value +"px;"; theCanvas.setAttribute("style", styleValue ); drawScreen(); } function createImageDataPressed(e) { var imageDataDisplay = document.getElementById("imageDataDisplay"); imageDataDisplay.value = theCanvas.toDataURL(); window.open(imageDataDisplay.value,"canvasImage","left=0,top=0,width=" + theCanvas.width + ",height=" + theCanvas.height + ",toolbar=0,resizable=0"); } } </script> </head> <body> <div style="display:none"> <video id="theVideo" autoplay="true" loop="true"> <source src="spaceeggs.ogg" type="video/ogg"/> </video> </div> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="300"> Your browser does not support HTML5 Canvas. </canvas> <form> Text: <input id="textBox" placeholder="your text" /> <br> Text Font: <select id="textFont"> <option value="serif">serif</option> <option value="sans-serif">sans-serif</option> <option value="cursive">cursive</option> <option value="fantasy">fantasy</option> <option value="monospace">monospace</option> </select> <br> Font Weight: <select id="fontWeight"> <option value="normal">normal</option> <option value="bold">bold</option> <option value="bolder">bolder</option> <option value="lighter">lighter</option> </select> <br> Font Style: <select id="fontStyle"> <option value="normal">normal</option> <option value="italic">italic</option> <option value="oblique">oblique</option> </select> <br> Text Size: <input type="range" id="textSize" min="0" max="200" step="1" value="50"/> <br> Fill Type: <select id="fillType"> <option value="colorFill">Color Fill</option> <option value="linearGradient">Linear Gradient</option> <option value="radialGradient">Radial Gradient</option> <option value="pattern">pattern</option> </select> <br> Text Color: <input class="color" id="textFillColor" value="FF0000"/> <br> Text Color 2: <input class="color" id="textFillColor2" value ="000000"/> <br> Fill Or Stroke: <select id="fillOrStroke"> <option value="fill">fill</option> <option value="stroke">stroke</option> <option value="both">both</option> </select> <br> Text Baseline <select id="textBaseline"> <option value="middle">middle</option> <option value="top">top</option> <option value="hanging">hanging</option> <option value="alphabetic">alphabetic</option> <option value="ideographic">ideographic</option> <option value="bottom">bottom</option> </select> <br> Text Align <select id="textAlign"> <option value="center">center</option> <option value="start">start</option> <option value="end">end</option> <option value="left">left</option> <option value="right">right</option> </select> <br> Alpha: <input type="range" id="textAlpha" min="0.0" max="1.0" step="0.01" value="1.0"/> <br> Shadow X:<input type="range" id="shadowX" min="−100" max="100" step="1" value="1"/> <br> Shadow Y:<input type="range" id="shadowY" min="−100" max="100" step="1" value="1"/> <br> Shadow Blur: <input type="range" id="shadowBlur" min="1" max="100" step="1" value="1" /> <br> Shadow Color: <input class="color" id="shadowColor" value="707070"/> <br> Canvas Width: <input type="range" id="canvasWidth" min="0" max="1000" step="1" value="500"/> <br> Canvas Height: <input type="range" id="canvasHeight" min="0" max="1000" step="1" value="300"/> <br> Canvas Style Width: <input type="range" id="canvasStyleWidth" min="0" max="1000" step="1" value="500"/> <br> Canvas Style Height: <input type="range" id="canvasStyleHeight" min="0" max="1000" step="1" value="300"/> <br> <input type="button" id="createImageData" value="Create Image Data"> <br> <br> <textarea id="imageDataDisplay" rows=10 cols=30></textarea> </form> </div> </body> </html>
What’s Next
In this chapter, we introduced you to the fundamentals of the HTML5 Canvas Text API, offered some general concepts relating to drawing on the canvas, and explained how to communicate with HTML form controls. As you can now see, the basic concept of writing text to HTML5 Canvas can be taken to very complex (and some might argue ludicrous) levels. The final application, Text Arranger 3.0, allows you to modify a single line of text in an almost infinite number of ways. In the next chapter, we move on to displaying and manipulating images on the canvas.
Get HTML5 Canvas now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.