1.1 History of HTML: From HTML1.0 to HTML5.0
1.2 HTML5 Structural elements
1.3 A Blog example: Mixing All Elements Together
1.4 The Details and Summary elements
1.5 Microdata
2.1 HTML5 Multimedia: Streaming with Video and Audio Elements
2.2 Subtitles and closed captions
2.3 Enhanced HTML5 media players and frameworks
2.4 Webcam, microphone: the getUserMedia API
3.1 Introduction - Module 3: HTML5 Graphics
3.2 Basics of HTML5 canvas
3.3 Immediate drawing mode: rectangles, text, images
3.4 Path drawing mode: lines, circles, arcs, curves and other path drawing methods
3.5 Colors, gradients, patterns, shadows, etc.
4.1 Introduction - Module 4: Animations
4.2 Basic animation techniques
4.3 Canvas and user interaction (keyboard, mouse)
4.4 A glimpse of advanced canvas functionalities
5.1 Introduction - Module 5: Forms
5.2 Elements and Attributes
5.3 Accessible Forms
5.4 <input> Types
5.5 Forms Attributes
5.6 Forms Elements
5.7 Form Validation API
6.1 Introduction - Module 6: HTML5 Basic APIs
6.2 The Web Storage API
6.3 The File API
6.4 The Geolocation API
W3C has designed a “Front-End Web Developer” (FEWD) Professional Certificate
where you learn all of the necessary skills needed to build interactive and responsive user experiences
on the Web. This program deepens your knowledge of the 3 foundational languages that power the Web:
HTML5, CSS and JavaScript.
The W3C FEWD program is composed of 5 courses:
This course is a natural follow up to the CSS Basics and HTML5 & CSS Fundamentals courses.
The HTML5 course team is thrilled to guide you in your learning experience. We are committed to teach you how to code Web pages, and how to do it the correct way. We encourage you to create Web pages and apps and share them in the discussion forums. Have fun!
While any text editor, like NotePad or TextEdit, can be used to create Web pages, they don’t necessarily offer a lot of help towards that end. Other options offer more facilities for error checking, syntax coloring and saving some typing by filling things out for you.
To help you practice during the whole duration of the course, we use the following interactive online editors. Pretty much all the course’s examples actually use these tools.
JS Bin is an open source collaborative Web development debugging tool. Most of the examples that are in this course are on JSBin.
Tutorials can be found on the Web such as this one or on YouTube.
The tool is really simple, just open the link to the provided examples, look at the code, look at the result, etc.
And you can modify the examples as you like, you can also modify / clone / save / share them.
Keep in mind that it's always better to be logged in (it's free) if you do not want to lose your contributions/personal work.
CodePen is an HTML, CSS, and JavaScript code editor that previews/showcases your code bits in your browser. It helps with cross-device testing, real-time remote pair programming and teaching.
Here's an article of interest if you use CodePen: Things you can do with CodePen [Brent Miller, February 6, 2019].
There are many other handy tools such as JSFiddle, and Dabblet. Please share your favorite tool on the discussion forum, and explain why! Share also your own code contributions, such as a nice canvas animation, a great looking HTML5 form, etc.
The term browser compatibility refers to the ability of a given Web site to appear fully functional on the browsers available in the market.
The most powerful aspect of the Web is what makes it so challenging to build for: its universality. When you create a Web site, you’re writing code that needs to be understood by many different browsers on different devices and operating systems!
To make the Web evolve in a sane and sustainable way for both users and developers, browser vendors work together to standardize newfeatures, whether it’s a new HTML element, CSS property, or JavaScript API. But different vendors have different priorities, resources, and release cycles — so it’s very unlikely that a new feature will land on all the major browsers at once. As a Web developer, this is something you must consider if you’re relying on a feature to build your site.
We are then providing references to the browser support of HTML5 features presented in this course using 2 resources: Can I Use and Mozilla Developer Network (MDN) Web Docs.
Can I Use provides up-to-date tables for support of front-end Web technologies on desktop and mobile Web browsers. Below is a snapshot of what information is given by CanIUse when searching for “CSS3 colors”.
Example of a CanIUse browser support table (using CSS3 colors);
To help developers make these decisions consciously rather than accidentally, MDN Web Docs provides browser compatibility tables in its documentation pages, so that when looking up a feature you’re considering for your project, you know exactly which browsers will support it.
For over 15 years, the W3C has been developing and hosting
free and open source tools used every day by millions of Web developers and Web
designers.
All the tools listed below are Web-based, and are available as downloadable
sources or as free services on the
W3C Developers tools site.
The W3C validator checks the markup validity of various Web document formats, such as HTML. Note that you are automatically directed to the Nu Html Checker when validating an HTML5 document.
The CSS validator checks Cascading Style Sheets (CSS) and (X)HTML documents that use CSS stylesheets.
Unicornis W3C’s unified validator, which helps people improve the quality of their Web pages by performing a variety of checks. Unicorn gathers the results of the popular HTML and CSS validators, as well as other useful services, such as Internationalization, RSS/Atom feeds and http headers.
The W3C Internationalization Checker provides information about various internationalization-related aspects of your page, including the HTTP headers that affect it. It will also report a number of issues and offer advice about how to resolve them.
The W3C Link Checker looks for issues in links, anchors and referenced objects in a Web page, CSS style sheet, or recursively on a whole Web site.
For best results, it is recommended to first ensure that the documents checked use valid (X)HTML Markup and CSS.
The W3C cheatsheet provides quick access to useful information from a variety of specifications published by W3C. It aims at giving in a very compact and mobile- friendly format a compilation of useful knowledge extracted from W3C specifications, completed by summaries of guidelines developed at W3C, in particular Web accessibility guidelines, the Mobile Web Best Practices, and a number of internationalization tips.
Its main feature is a lookup search box, where one can start typing a keyword and get a list of matching properties/elements/attributes/functions in the above-mentioned specifications, and further details on those when selecting the one of interest.
Most of the technologies you use when developing Web applications and Web sites are designed and standardized in W3C in a completely open and transparent process.
In fact, all W3C specifications are developed in public GitHub repositories, so if you are familiar with GitHub, you already know how to contribute to W3C specifications! This is all about raising issues (with feedback and suggestions) and/or bringing pull requests to fix identified issues.
Contributing to this standardization process might be a bit scary or hard to approach at first, but understanding at a deeper level how these technologies are built is a great way to build your expertise.
If you’re looking to an easy way to dive into this standardization processes, check out which issues in the W3C GitHub repositories have been marked as “good first issue” and see if you find anything where you think you would be ready to help.
Another approach is to go and bring feedback ideas for future technologies: the W3C Web Platform Community Incubator Group was built as an easy place to get started to provide feedback on new proposals or bring brand-new proposals for consideration.
Happy Web building!
As steward of global Web standards, W3C’s mission is to safeguard the openness, accessibility, and freedom of the World Wide Web from a technical perspective.
W3C’s primary activity is to develop protocols and guidelines that ensure long- term growth for the Web. The widely adopted Web standards define key parts of what actually makes the World Wide Web work.
Tim Berners-Lee wrote a proposal in 1989 for a system called the World Wide Web. He then created the first Web browser, server, and Web page. He wrote the first specifications for URLs, HTTP, and HTML.
In October 1994, Tim Berners-Lee founded the World Wide Web Consortium (W3C) at the Massachusetts Institute of Technology, Laboratory for Computer Science [MIT/LCS] in collaboration with CERN, where the Web originated (see information on the original CERN Server, with support from DARPA and the European Commission.
In April 1995, Inria became the first European W3C host, followed by Keio University of Japan (Shonan Fujisawa Campus) in Asia in 1996. In 2003, ERCIM took over the role of European W3C Host from Inria. In 2013, W3C announced Beihang University as the fourth Host.
In addition to these four Host locations that employ W3C staff, there are W3C Offices around the globe that support the developer communities in their regions and organize local events. Find the one next to your place!
As of June 2020, W3C:
People often use the words “Internet” and “Web” interchangeably, but this usage is technically incorrect.
The Web is an application of the Internet.
The Web is the most popular way of accessing the Internet, but other applications of the Internet are e-mail and ftp for example.
One analogy equates the Internet to a road network where the Web is a car, the email
is a bicycle, etc.
Read this article for more details about the
difference between Internet and the Web.
The internet is a global network of billions of servers, computers, and other hardware devices. Each device can connect with any other device as long as both are connected to the internet using a valid IP address. The internet makes the information sharing system known as the web possible.
The web, which is short for World Wide Web, is one of the ways information is shared on the internet (others include email, File Transfer Protocol (FTP), and instant messaging services. The web is composed of billions of connected digital documents that are viewed in a web browser, such as Chrome, Safari, Microsoft Edge, Firefox, and others.
Think of the internet as a library. Think of the books, magazines, newspapers, DVDs, audiobooks, and other media it contains as websites.
Both the internet and the web serve unique purposes but work hand in hand to provide information, entertainment, and other services to the public.
The internet really is the information superhighway. It passes through various kinds of network traffic including, FTP, IRC, and the World Wide Web. Without it, we wouldn’t have our favorite and most common way to access websites.
The internet was born in the 1960s under the name ARPAnet. It was an experiment by the U.S. military to find ways to maintain communications in the case of a nuclear strike. With a decentralized network, communications could be maintained even if parts were taken offline. ARPAnet eventually became a civilian effort, connecting university mainframe computers for academic purposes.
As personal computers became mainstream in the 1980s and 1990s and the internet was opened to commercial interests, it grew exponentially. More and more users plugged their computers into the massive network through dial-up connections, then through faster connections such as ISDN, cable, DSL, and other technologies. Today, the internet has grown into a public spiderweb of interconnected devices and networks.
No single entity owns the internet, and no single government has absolute authority over its operation. Some technical rules, and its hardware and software standards, are agreed upon by invested organizations, groups, businesses, and others. These groups help the internet remain functional and accessible. However, for the most part, the internet is a free and open broadcast medium of networked hardware with no single owner.
Most consumers are familiar with and comfortable with the World Wide Web. With its easy-to-use interface, it’s the best way to get information in a few clicks.
The World Wide Web was born in 1989. Interestingly enough, the web was built by research physicists so that they could share research findings with one another’s computers. Today, that idea has evolved into the greatest collection of human knowledge in history.
The credited inventor of the World Wide Web is Tim Berners-Lee.
You have to access the internet to view the World Wide Web and the web pages or other content it contains. The web is the collective name for all the pages, sites, documents, and other media that are served to visitors.
The web consists of digital documents, referred to as web pages, that are viewable through web browser software on devices like smartphones, tablets, and computers. These pages contain many types of content, including static content like encyclopedia pages, but also dynamic content like eBay sales, stocks, weather, news, and traffic reports.
A collection of connected web pages that are publicly accessible and under a single domain name is referred to as a website.
Web pages are connected using Hypertext Transfer Protocol (HTTP), the coding language that allows you to visit any public web page. By clicking a hyperlink or entering a Uniform Resource Locator (URL), the browser uses this unique address to find and access a web page. Search engines like Google make it easy to filter the billions of web pages now populating the web by locating the articles, videos, and other media you want to find based on your search criteria.
Plain and simple, the internet allows access to the World Wide Web. Without it, we have no way of accessing the thousands of websites out there. For most online needs, however, the web is the easiest to use. Each serves an important purpose.
The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect.
The Web has become an essential aspect of our daily lives, and everyone should have access to this technology. Web accessibility focuses on ensuring equivalent access for people with disabilities. It is increasingly important to many organizations and governments from around the world, and has many business benefits. Access to information, including on the Web, is also recognized by the UN Convention on the Rights of Persons with Disabilities (CRPD).
Web accessibility addresses all disabilities, including hearing, learning and cognitive, neurological, physical, speech, and visual disabilities. Some examples of Web accessibility features include:
Web accessibility features also benefit many more users, such as:
The Web is an increasingly important resource in many aspects of life: education, employment, government, commerce, health care, recreation, and more. When Web pages, Web technologies, Web tools, or Web applications are badly designed, they can create barriers that exclude people from using the Web. More information is available in the W3C Accessibility overview.
There are many simple Web accessibility improvements that you can implement and check right away, even when you are new to this topic.
Remember that when developing or redesigning a website or Web application, it is best to evaluate accessibility early and throughout the development process to identify accessibility problems early, when it is easier to address them.
Two examples are provided below but you can find more tips and information in these 2 resources:
Good page titles are particularly important for orientation — to help people know where they are and move between pages open in their browser. The first thing screen readers say when the user goes to a different Web page is the page title. In the Web page markup, they are the words <title> within the <head>.
Example:
<head>
...
<title>Web Accessibility Initiative (WAI) - home page</title>
...
</head>
Text alternatives (“alt text”) are a primary way of making visual information accessible, because they can be rendered through any sensory modality (for example, visual, auditory or tactile) to match the needs of the user. Providing text alternatives allows the information to be rendered in a variety of ways by a variety of user agents. For example, a person who cannot see a picture can have the text alternative read aloud using synthesized speech.
Example: See the W3C logo below. It contains a link that points to the W3C Web site. The text alternative is going to be a brief description of the link target.
<a href="https://w3.org">
<img src="https://w3.org/Icons/w3c_home.png" width="72" height="48"
alt="World Wide Web Consortium">
</a>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Page Title</title>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
... <!-- The rest is content -->
As Web site layouts evolve, HTML5 structural elements such as lists, paragraphs,
tables, etc. show their limits.
Today, many Web sites offer navigation menus, tabbed panels, headers, footers, and so on.
The way these “parts”’ are implemented relies heavily on <div>
and <span> elements with different id and class attributes, lots of
CSS and lots of JavaScript code to apply custom styles and behaviors.
However, there are some issues with this approach:
Even if differences exist between ids, classes and css/js implementations, they also share common behaviors, layouts, and “ways of doing things” that could be guessed at first glance by a human.
So various studies have been conducted in order to identify the most popular ids, class names, widgets, etc. used on the Web:
Quoting from this article: “During the creation of HTML5, Ian Hickson used Google’s tools to mine data from over a billion Web pages, surveying what ids and class names are most commonly used on the real world Web. Opera did a similar study of 3.5 million URLs, calling it MAMA (”Metadata Analysis and Mining Application”). MAMA, as structural Web-paged search engine, had a smaller URL set, but looked at a larger and wider variety of Web page statistics”.
The results of these surveys led to the addition of new structural elements in HTML5. For example, the very popular <div class=“header”> led to the creation of a <header> element, <div class=“aside”> to a <aside> element, etc.
Finally, the 20 most popular ids and class names found in Hickson’s and Opera’s surveys gave birth to these new elements (click on the element’s name to go to the W3C specification about this element):
HTML5 element | Description |
---|---|
<header> | Introduction of “sectioning elements”: an article, a section, the entire document (header page). Typically the header of a Web site that appears on the top of each page, or a header of a long <article> or of a long <section>. |
<footer> | Contains the footer of a site, a long <article>, or a long <section>. |
<nav> | Section that contains the main navigation links (within the document or to other pages). |
<article> | Independent content, which can be individually extracted from the document and syndicated (RSS or equivalent) without penalizing its understanding. Typically a blog post. |
<section> | Generic section used to group different articles for different purposes or subjects, or to define the different sections of a single article. Generally used with a header. |
<time> | Used for marking up times and dates. |
<aside> | Section whose content is not necessarily directly related to the main content that surrounds it, but can provide additional information. |
<figure> and <figcaption> | Used to encapsulate a figure as a single item, and contains a caption for the figure, respectively. |
<main> | The main element represents the main content of the body of a document or application. The main content area consists of content that is directly related to or expands upon the central topic of a document or central functionality of an application. There can be only one <main> element in a document. |
And there is no <content> element even though the <div class=“content”> was very popular. Instead, the HTML5 group decided that anything not embedded in one of the elements from the above table is “default content”.
If the content is of a type that corresponds to one of the elements from the table, i.e. if the content is an article, it should be embedded between <article> and </article>.
Read also at the end of this section about the new <main> element. This element is part of the HTML5 recommendation and an integral part of the HTML document structure.
Let’s study an example we put on JSBin (all examples we have cooked up are available on the jsbin.com Web site and can be modified freely: you can save your own version using the “Bins/create milestone” menu, share your version with others in the forums, etc. Don’t hesitate to play with the source code, you will never break anything).
This is an example of one way to organize a blog. Here, we have designed the HTML page using a <header> element that contains the “Simple HTML5 blog” text that appears on top of the page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Simple HTML5 blog</title>
</head>
<body>
<header>
<h1>Simple <span>HTML5</span> blog</h1>
</header>
...
header {
color: #007e99;
font-size: 2.5em;
padding: 20px 50px
}
header span {
color: #722
}
The navigation menu just below the header is a <nav> element. For the purpose of this example we haven’t provided any value for the hyperlinks…
1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="utf-8"/>
5. <title>Simple HTML5 blog</title>
6. </head>
7. <body>
8. <header>
9. <h1>Simple <span>HTML5</span> blog</h1>
10. </header>
11. <nav>
12. <ul>
13. <li><span>Blog</span></li>
14. <li><a href="">About</a></li>
15. <li><a href="">Contact</a></li>
16. </ul>
17. </nav>
1. nav {
2. font-size: 1.5em;
3. margin: 5px 0;
4. padding: 20px 50px
5. }
6. nav li {
7. display: inline;
8. margin: 0 15px
9. }
10. nav li:first-child {
11. margin-left: 0
12. }
13. * html nav ul {
14. margin-left: -15px
15. }
16. nav span, nav a {
17. padding: 3px 15px 4px
18. }
19. nav span {
20. background: #722;
21. color: #fff
22. }
Now, we have one big <section> element that contains a set of <article> elements…
<section>
<article>
...
</article>
<article>
...
</article>
<article>
...
</article>
</section>
1. section {
2. float: left;
3. padding: 35px 0;
4. position: relative;
5. width: 70%
6. }
7. section article {
8. margin: 0 50px 40px;
9. padding: 25px 0 0;
10. position: relative
11. }
12. section header {
13. font-size: 1em;
14. padding: 0;
15. }
16. section h2 {
17. font-size: 2.3em;
18. }
Note that the H2, article, article header, etc. will be styled using these rules.
Next, in each article in the section we have a header (to display the article title), paragraphs (article content), and so on.
1. <section>
2. <article>
3. <header>
4. <h2><a href="">Information about this example</a></h2>
5. </header>
6. <p>Try to move the mouse on different elements. The structure will be
7. highlighted and you will be able
8. to see the different inclusions of elements one in each other. If you
9. move the cursor to this sentence, it will be highlighted in dark grey,
10. showing the presence of an <article> element, surrounded by a
11. <section> element (light grey), etc. So we have some articles in
12. a single section element. The page title at the top is a <header>
13. element, while the tag cloud on the right is a <aside> element. The
14. main menu on top (with Blog, About, Contact) is a <nav> element.</p>
15. <figure>
16. <img src="HTML5-tags.png"
17. alt="Example of HTML5 structural tags" />
18. <figcaption>
19. Fig. 1 : an example of how new structural elements could
20. be used. This page put a <nav> on top, and does not have
21. headers and footer for each article, like in this figure,
22. but it could... By the way this is a
23. <figcaption> inside a <figure> element...
24. </figcaption>
25. </figure>
26. </article>
27. ...
28. </section>
Also note the way we included a figure using the new “HTML5” method, using a <figure>..<figure> element that embedded a <img src=…> element together with a <figcaption> element.
Here is the CSS for the <figcaption> element we have used in the example (we did not apply any style to the <figure> element):
<figure>
<img src="HTML5-tags.png"
alt="Example of HTML5 structural tags" />
<figcaption>
Fig. 1 : an example of how .....
</figcaption>
</figure>
figcaption {
font-style:italic;
font-size: 0.8em;
width: 100%
}
After the long <section> element that contains all the blog articles displayed in the page, we added the HTML code for the tag cloud that is displayed on the right of the page, “aside”! This is done using - you already guessed it - an <aside> element:
1. <section>
2. .... all <article>... </article> here....
3. </section>
4. <aside>
5. <h2>Tag cloud</h2>
6. <ul class="tag-cloud">
7. <li><a href="" rel="tag" class="w2">ajax</a></li>
8. <li><a href="" rel="tag" class="w8">apple</a></li>
9. <li><a href="" rel="tag" class="w3">css</a></li>
10. ...
11. </ul>
12. </aside>
13. ...
We are not going to show the complete CSS here as it uses some tricks to display the list as a “real tag cloud” that uses JavaScript for handling events, etc. Those who are curious can look at the code of the online example.
1. aside {
2. float: right;
3. padding: 70px 0 30px;
4. position: relative;
5. width: 25%
6. }
7. aside h2 {
8. color: #888;
9. font-size: 1.8em
10. }
11. aside .tag-cloud {
12. padding: 15px 35px 10px 0;
13. text-align: center
14. }
15. ...
We used a float:right CSS rule to put the tag cloud on the right… In a following section we will provide several examples that explain how to make a nice layout with the new structural elements, using simple CSS rules.
1. <html>
2. ...
3. <body>
4. ...
5. <section>
6. ...
7. </section>
8. <aside>
9. ...
10. </aside>
11. <footer>
12. <p>© 2009 Some blog</p>
13. </footer>
14. </body>
15. </html>
footer {
clear: both;
color: #777;
padding: 10px 50px
}
It may not be clear whether a <section> may contain one or several <article> elements or if an <article> may contain one or several <section> elements.
1. <article id="id1">
2. <section id="id1part1">
3. <h2>Introduction</h2>
4. </section>
5. <section id="id1part2">
6. <h2>My travel to India</h2>
7. </section>
8. <section id="id1part3">
9. <h2>Return to France</h2>
10. </section>
11. </article>
The blog example from the previous part of the course, on the other hand, uses a single <section> that contains several <article> elements. Indeed, we can also have a <section> that regroups all blog posts per month, each one being an <article> element.
Yes, you can, in case you would like to propose some navigation links with each blog post, for example:
1. <article>
2. <header>
3. <h1>Blog post title</h1>
4. <p>Author: Michel</p>
5. </header>
6. <nav>
7. <ul>
8. <li><a href="...">Next post</a></li>
9. <li><a href="...">Previous post</a></li>
10. <li><a href="...">Contact author</a></li>
11. </ul>
12. </nav>
13. <p>Content...</p>
14. <footer>
15. <p>Posted by Michel, the <time datetime="2012-02-02">February 2,
16. 2012</time> </p>
17. </footer>
18. </article>
In that case, the <nav> element proposes navigation links to the next or previous blog post, as well as a link to contact the author of the blog post.
Also note that we used in that example a <footer> element in the blog post.
The new elements have been primarily designed to better structure the code of HTML pages such as those generated by blog or CMS software, however do not forget that they add new semantics and will be taken into account by:
You can use <div> elements in all cases where the proposed structural elements do not fit your needs: for defining some content that should be styled, for example.
This chart from the HTML5 Doctor Web site may help you decide whether or not to use a <div>:
We will now present some best practices for starting to use <section>, <article>, <nav>, <aside>, in particular concerning the use of headings (h1, h2, h3, h4, h5 and h6).
Since the very beginning, HTML has had heading elements: <h1>…<h6>. These elements are used to display headings with different sizes by default, when no CSS is used. The following example shows 6 sentences that are surrounded by <h1>, <h2>, <h3>, <h4>, <h5> and <h6>:
These headings define a hierarchy, as shown by the default sizes given by the browser. This hierarchy can also be used to define an outline of the document. To illustrate this, we have used a browser extension. Here is the result for the previous example:
In the above outline, note that we have only used H1… H6 elements, without any new HTML5 structural elements such as <section> or <article>.
Here is a list of browser extensions you can try, for visualizing the outline of a document: table-of-contents-crx Chrome extension or this Firefox extension.
The <section>, <article>, <nav> and <aside> elements are called “sectioning elements”. They cut a document into slices we call “sections”.
The HTML5 specification says that “each sectioning element potentially has a heading and has also an outline associated”.
<h1> … <h6>
are called headings, and define the header of a section (whether explicitly marked up using sectioning content elements, or implied by the heading content itself). This means that:
<body>
<h1>Title of my document</h1>
...
</body>
<body>
...
<section>
<h1>Title of my section</h1>
...
</section>
</body>
The first element of a heading content in an element of sectioning content represents the heading for that section (the <section><h1>…</h1></section> in the above example).
Subsequent headings of equal or higher rank start new (implied) sections, headings of lower rank start implied subsections that are part of the previous one. In both cases, the element represents the heading of the implied section.
1. <body>
2. <section>
3. <h1>This H1 is the heading of an explicit section</h1>
4. ...
5. <h2>This H2 is a subheading, part of the same section
6. (lower rank)</h2>
7. ....
8. <h1>This H1 starts an implicit new section in the explicit
9. section (equal or higher rank)</h1>
10. ...
11. <h2>This is a H2 heading in the new section that has
12. just started</h2>
13. ...
14. </section>
15. </body>
In the above example, please note two things:
It’s always better - mainly for accessibility reasons - to include a heading (a <h1>, <h2>…<h6>) in each sectioning element (<section>, <article>, <nav>, <aside>), but also after the <body> element (called a “sectioning root”).
<section>
<h1>Blog post of April 2020</h1>
...
</section>
<section>
<b><header></b>
<b><h1>Blog post of April 2020</h1></b>
<b><p>Posted by Michel Buffa...</p></b>
<b></header></b>
...
</section>
<section>
<header>
<p class="article title">Blog post of April 2020</p>
<p>Posted by Michel Buffa...</p>
</header>
...
</section>
The last example is bad for accessibility reasons. A screen reader that vocalizes the page will just say “Entering section”, while in the previous two good examples it would say “entering section with heading Blog Posts of April 2020”. You can also check if your headings and sectioning elements are ok by using a browser extension that displays the outline of the document (just search for “html5 outliner” in your browser’s extension search engine).
UPDATE: For the course screenshots, we used the Google Chrome HTML5 outliner extension that is no more available (it has been removed by its developer), but you can use any other equivalent extension such as table-of-contents-crx for Chrome or Outline sidebar for Firefox.
Notice that <body> is also a sectioning element. It’s called a “sectioning root”, and would also need a heading.
<body>
<b><h1>Example Blog</h1></b>
<section>
<header>
<b><h2>Blog post of April 2020</h2></b>
<p>Posted by Michel Buffa...</p>
</header>
<p>Content of the blog post...</p>
</section>
</body>
The sectioning root (<body>) and the sectioning elements (<section> here…), each have a heading.
The <header> element is just a container. It is not taken into account for defining new sections of a document nor does it affect the hierarchy levels.
You can use heading elements <h1>…<h6> in a <header> but be careful if you use more than one, as the rules explained in the previous part of the course will apply and may generate implicit “sections” in the header.
1. <section>
2. <header>
3. <h1>Some text in a h1 in a header of a section</h1>
4. <h2>This a h2 in the header...</h2>
5. </header>
6. </section>
Here is the resulting table of contents, notice the two subsections that appear, one for the H1, one for the H2:
Indeed, HTML does not have a dedicated mechanism for marking up subheadings, alternative titles or taglines.
If you do not want the subtitles to be included in the table of contents, just use standard markup, for example <p> elements, as shown in the next example. Of course, CSS rules can be applied to change colors, sizes, etc.
1. <header>
2. <h1>HTML 5.1 Nightly</h1>
3. <p>A vocabulary and associated APIs for HTML and XHTML</p>
4. <p>Editor's Draft 9 May 2013</p>
5. </header>
The example below defines several implicit “sections” by using <Hx> directly (at lines 7 and 9):
1. <body>
2. <h4>Apples</h4>
3. <p>Apples are fruit.</p>
4. <section>
5. <h2>Taste</h2>
6. <p>They taste lovely.</p>
7. <h6>Sweet<h6>
8. <p>Red apples are sweeter than green ones.</p>
9. <h1>Color</h1>
10. <p>Apples come in various colors.</p>
11. </section>
12. </body>
1. <body>
2. <h1>Apples</h1>
3. <p>Apples are fruit.</p>
4. <section>
5. <h2>Taste</h2>
6. <p>They taste lovely.</p>
7. <section>
8. <h3>Sweet</h3>
9. <p>Red apples are sweeter than green ones.</p>
10. </section>
11. </section>
12. <section>
13. <h2>Color</h2>
14. <p>Apples come in various colors.</p>
15. </section>
16. </body>
Here we propose a small piece of JavaScript code you can use in your documents to display an embedded table of contents.
This example is a simple document, with a hyperlink that, once clicked, displays the table of contents in an <aside> element in the main <section>. Just look at the source code and copy/paste the link into your own HTML documents.
1. <body>
2. <h1>This is an example of embedded table of content</h1>
3. <section>
4. <header>
5. <h1>First section of the document (this is a h1)</h1>
6. This is a subheading...
7. </header>
8. <h2>First subsection of the first section (a h2)</h2>
9. <p>Blah Blah...</p>
10. </section>
11. <section>
12. <h1>Second section of the document (a h1)</h1>
13. <h2>First subsection (a h2)</h2>
14. </section>
15. <aside>
16. <h3>Table of contents</h3>
17. <a href="javascript:(function(){...})();"
18. title="TableDeMatiere">
19. Click here to display the table of contents!
20. </a>
21. </aside>
22. </body>
Best practice: visualizing the table of contents is useful for debugging the structure of your page, and checking the presence of headings after sectioning content.
Indeed, tools that generate the table of contents are a good way to debug the structure of your page. Is the hierarchy correct? Is it what I wanted when I designed my page?
They are also useful for checking the presence of headings in each sectioning content. If some headings are missing, the table of contents will display some "untitled entries". Remember that having a heading after each sectioning content is a good practice in terms of accessibility.
If you use <nav> / <header> / <footer> etc. to structure your document, you can also use <main> to identify the main content of the document. Doing so provides a navigable document structure for assistive technology users as well as styling hooks for devs.
We have seen the different sectioning elements of HTML5, so why didn’t we talk about the <main> element earlier in this part of the course? Shouldn’t <main>…</main> be used in place of <div class="main">…</div>?
The <main> element is supported by major modern browsers (see the corresponding support table on CanIUse and MDN’s brower compatibility page.
This element is subject to some constraints:
And finally, here are some examples (from the HTML5 specification) that mix the <main> element with the other sectioning elements already seen in the course:
1. <!-- other content -->
2.
3. <main>
4.
5. <h1>Skateboards</h1>
6. <p>The skateboard helps kids to get around.</p>
7.
8. <article>
9. <h2>Longboards</h2>
10. <p>Longboards are a type of skateboard with a longer
11. wheelbase and larger, softer wheels.</p>
12. <p>... </p>
13. <p>... </p>
14. </article>
15.
16. <article>
17. <h2>Electric Skateboards</h2>
18. <p>These no longer require the propelling of the skateboard by means of the feet; rather an electric motor propels the board, fed by an electric battery.</p>
19. <p>... </p>
20. <p>... </p>
21. </article>
22.
23. </main>
24.
25. <!-- other content -->
Here is another example (also from the specification). Here the <main> element contains a <nav> element consisting of links to subsections of the main content:
1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="utf-8"/>
5. <title>Graduation Ceremony Summer 2022</title>
6. </head>
7. <body>
8. <header>The Lawson Academy:
9. <nav>
10. <h2>Click these links to navigate...</h2>
11. <ul>
12. <li><a href="courses.html">Courses</a></li>
13. <li><a href="fees.html">Fees</a></li>
14. <li><a>Graduation</a></li>
15. </ul>
16. </nav>
17. </header>
18. <main>
19. <h1>Graduation</h1>
20. <nav>
21. <h2>Please choose:</h2>
22. <ul>
23. <li><a href="#ceremony">Ceremony</a></li>
24. <li><a href="#graduates">Graduates</a></li>
25. <li><a href="#awards">Awards</a></li>
26. </ul>
27. </nav>
28. <h2 id="ceremony">Ceremony</h2>
29. <p>Opening Procession</p>
30. <p>Speech by Valedictorian</p>
31. <p>Speech by Class President</p>
32. <p>Presentation of Diplomas</p>
33. <p>Closing Speech by Headmaster</p>
34. <h2 id="graduates">Graduates</h2>
35. <ul>
36. <li>Eileen Williams</li>
37. <li>Andy Maseyk</li>
38. <li>Blanca Sainz Garcia</li>
39. <li>Clara Faulkner</li>
40. <li>Gez Lemon</li>
41. <li>Eloisa Faulkner</li>
42. </ul>
43. <h2 id="awards">Awards</h2>
44. <ul>
45. <li>Clara Faulkner</li>
46. <li>Eloisa Faulkner</li>
47. <li>Blanca Sainz Garcia</li>
48. </ul>
49. </main>
50. <footer>Copyright 2023 B.Bauska</footer>
51. </body>
52. </html>
For accessibility matters, a best practice is to split your page content into “regions” defined by the five 5 elements (aside, footer, header, main and nav) learned this week.
We recommend this article written by Steve Faulkner: “Easy content organisation with HTML5” (24 September 2015). Steve explains in details how to organize an HTML document into “regions” based on the semantic markup elements we have seen so far during Module 1 of this course.
Let’s go back to our blog example and see what can be improved:
The blog example is online at JSBin: let’s see below what the Google Chrome HTML5 Outliner extension showed.
Also note that in this example, we used H1s after each sectioning element, and we still get a hierarchy, some H1s are inside an <article> that is in a <section> (this corresponds to the third example given in the "heading and sectioning elements" part of the course):
1. <section>
2. <header>
3. <b><h1>Blog posts for April 2012</h1></b>
4. </header>
5. <article>
6. <header>
7. <b><h1><a href="">Information about this example</a></h1></b>
8. This example is a modified version of <a href="https://example.com/blog/index.html">https://example.com/blog/index.html</a>
9. </header>
10. ...
11. </article>
12. </section>
With this technique, parts of the document can be moved more easily, or integrated inside an RSS stream, without the need to renumber the headings.
Beware that this technique will require you to use some CSS styling, and may confuse some screen readers that do not yet take into account this way of computing the heading hierarchy. A simple fix is to use an H1 right after the <body> and use only H2…H6 inside <section>, <article>, <nav> and <aside>.
We need to add a heading in the <nav> element. This will both fix the outline of the document by removing the untitled entry, and will also make screen readers happy as they will better vocalize the structure of the page (it will say “entering nav” followed by the vocalization of the heading content).
1. <nav>
2. <header>
3. <h1>Navigation menu</h1>
4. <b>
5. </header></b>
6. <ul>
7. <li><span>Blog</span></li>
8. <li><a href="">About</a></li>
9. <li><a href="">Contact</a></li>
10. </ul>
11. </nav>
A common remark from Web designers is: “we do not want a heading content displayed systematically after a <nav>, or an <aside> element…”
BEST PRACTICE #1: In order to NOT display the heading content on screen the recommended technique is described in this article by Steve Faulkner. Do not use display:none or visibility:hidden in your CSS stylesheet, as in that case the heading content will never be vocalized by screen readers, and more generally by assistive technologies.
As an illustration of the recommended technique, see this JSBin version of the blog example that hides the <h2>Navigation menu</h2> from the <nav>…</nav> element, using the CSS technique explained in the above link.
BEST PRACTICE #2: It is not advised to include interactive content (links, controls etc) that is hidden offscreen it is in fact a violation of the W3C WCAG 2.0 Guidelines. All interactive content must have a visible focus indicator (and be on screen when focused).
In the previous section, we saw how to embed a table of contents using some JavaScript code borrowed from the Google Chrome HTML5 outliner extension.
Let’s add this piece of code (we removed the JS details from this extract):
<aside> <h1> <a href="javascript:(function(){...});" title="TableOfContents"> Click here to display the table of contents! </a> </h1> </aside>
<main> <section> <header> <h2>Blog posts for April 2012</h2> </header> ... </main>
As explained in the article HTML5 Document Outline and in the W3C HTML Wiki, it is risky to use nested H1s, as browsers do not correctly implement the “outline algorithm”.
The blog example uses nested H1’s. If you check it with the W3C conformance checker, it issues a warning: "Consider using the h1 element as a top-level heading only (all h1 elements are treated as top-level headings by many screen readers and other tools)."
While this is just a warning, we do prefer to use H1s only as top level elements, and replace the H1s we had after <section>, <article>, <nav> and <aside> elements respectively by a H2s and H3s.
<nav> <header> <h2>Navigation menu</h2> </header> ... </nav>
In this section, we show some "classic" CSS layout techniques for designing an HTML page that uses the new sectioning elements.
We embed examples from this very good post about "Positioning content". This is a recommended reading as it details how to use the CSS float property to layout a Web page.
The 4 examples below are given “as is” to give you some hints. There are lots of other possibilities on using CSS to position element.
This example uses the following HTML structure (notice that we use the "HTML entity syntax" for displaying "<" or ">". For example, "<" displays a "<" character).
1. <header> 2. <code><header></code> 3. </header> 4. 5. <section> 6. <code><section> <br> float: left;</code> 7. </section> 8. 9. <aside> 10. <code> right;</code> 11. </aside> 12. 13. <footer> 14. <code><footer></code> 15. </footer>
Here we use the CSS rule float:left for the <section> and the CSS rule float:right for the <aside>. When an element floats, it goes out of the normal flow of the HTML element. Then by default it floats to the edge of its parent; and its size depends on the elements it contains. In order to fill the whole horizontal space, we prefer here to “force the width” by setting the CSS width property with a percentage. We took width: 63% for the <section> on the left and width:30% for the <aside> on the right.
You can look at the complete CSS code in the interactive example below (click on the CSS or HTML text in the menu bar below, or click “edit on codepen” to change the code and see the results):
Also available online at JSBin.
Here we show how to make a 3 column layout using the CSS float property.
1. <header> 2. <code><header></code> 3. </header> 4. 5. <section> 6. <code><section> <br> float: left;</code> 7. </section> 8. 9. <section> 10. <code><section> <br> float: left;</code> 11. </section> 12. 13. <section> 14. <code><section> <br> float: left;</code> 15. </section> 16. 17. <footer> 18. <code><footer></code> 19. </footer>
Instead of having one element with a float:left and one element with a float:right property, we instead use float:left for all three of them, and we give a width:30% CSS property value to each <section>. We also set a small margin so that the colums have a gap between them.
This example uses the CSS flex property to achieve a result similar to the one shown in Example 2. There are many articles on Flexbox and we recommend those from Rachel Andrew on Smashing Magazine: “ Use cases for Flexbox”, “Flexbox: how big is that flexible box”, etc.
This example also uses all the structuring elements we saw: main, article, section, etc. It uses only the simplest parts of the FlexBox CSS module, so it should be easy to understand, even for CSS beginners:
These elements have been introduced for displaying a foldable zone in an HTML document.
In the screenshot below, taken from the W3C specification page, the text next to the horizontal arrow is a <summary> element, and the text displayed when we click on the summary part, is the <details> element. This is a sort of “accordion” with foldable content.
The <details> element generates a simple widget to show/hide element contents, optionally by clicking on its child <summary> element.
Here is an example of what can be done using these elements: see the online version on JSBin:
And here is what is displayed after clicking on the small arrow-shaped icon to the left of the summary:
1. <!DOCTYPE html> 2. <html lang="en"> ... 3. <body> 4. <details> 5. <summary> 6. How to beat the boss...spoiler alert ! 7. </summary> 8. <p> Just aim to the red spots near his eyes</p> 9. <p>Keep shooting at these spots until the eyes open, then hit quickly both eyes with your laser beam.</p> 10. </details> 11. </body> 12. </html>
The <summary>…</summary> is inside a <details>…</details> element. By clicking on the icon at the left of the summary, the content of the <details> value is displayed/hidden.
<details> blocks can be bembedded inside one another, like in this example:
1. <details> 2. <summary> 3. How to beat the boss...spoiler alert ! 4. </summary> 5. <p> Just aim to the red spots near his eyes</p> 6. <p>Keep shooting at these spots until the eyes open, then hit quickly both eyes with your laser beam.</p> 7. <details> 8. <summary> 9. Bonus and spoiler No 2: get a new weapon by cutting the tail of the boss. 10. </summary> 11. <p>Before finishing him, try to cut his trail, you will get a new weapon</p> 12. <p>Just try to stay behind him as long as you can, hitting his tail with your melee weapon, 13. after a few hits the trail will fall and you will get a new bonus weapon, then finish the boss.</p> 14. </details> 15. </details>
There are CSS pseudo classes to style this icon when it is in the open or closed state. Support for these is still incomplete as of June 2020 (works on Google Chrome, Opera, Safari, not in FF).
The color and background of the icon on the left are specified by the following CSS rule, which uses the pseudo class ::-webkit-details-marker
In this example: red arrow, white background.
summary::-webkit-details-marker { color:#FF0000; background:#FFFFFF; }
Once opened, the selector details [open] can style the icon when <details> is unfolded. In this example: blue arrow, turquoise background.
details[open] summary::-webkit-details-marker { color:#0000FF; background:#00FFFF; }
It is also possible to change the icon itself using the CSS pseudo class :after
Use a “+” shaped icon, pink, bold, etc… :
summary:after { content: "+"; color: #FF00FF; float: left; font-size: 1.5em; font-weight: bold; margin: -5px 5px 0 0; padding: 0; text-align: center; width: 20px; }
Use a “-” shaped icon, white, when details are displayed:
details[open] summary:after { content: "-"; color: #FFFFFF }
The <time> element is useful for marking a time or a duration in a document.
It provides both a human readable part (the part between <time> and </time>) and a machine readable part contained within a datetime attribute. Dates are expressed as YYYY-MM-DD.
The machine readable part adds semantics that can be used by search engines for indexing, by browsers or by browser extensions, or by JavaScript code. Useful scenarios include generating alerts for birthdays, automatically adding dates or events that contain <time> elements in a calendar, etc.
1. We open at <time>10:00</time> every morning. 2. I have a meeting the <time datetime="2020-02-14">Monday 14/02/2020.</time>. 3. Blog posts from the year <time datetime="2020">2020</time>. 4. Archives, blog posts for <time datetime="2020-04">April 2020</time> 5. This recipe was published by Michel the <time datetime="2020-04-16">April 16, 2020</time>.
The datetime attribute can be used for indicating a date/time or a duration.
Supports different specifications of time such as “a year”, “a month in a year”, “a week in a year”, “a time”, etc…
datetime attribute values | Interpretation |
---|---|
<time datetime=“2020”> | The year 2020 |
<time datetime=“2020-11”> | November 2020 |
<time datetime=“11-13”> | November 13th (any year) |
<time datetime=“2020-W21”> | Week 21 from year 2020 |
<time datetime=“2020-11-13 09:00”> | November 13th year 2020, time = 9:00 |
<time datetime=“2020-11-13TO9:00”> | Same as prev ex, both syntaxes are supported, with and without the “T” between date and time |
<time datetime=“09:00%”> | 9:00 in the morning, GMT |
<time datetime=“09:00-05”> | 9:00 in the morning, GMT minus 5 hours |
<time datetime=“09:00+05:45”> | 9:00 in the morning, GMT plus 5 hours 45 mins, (for example, Nepal is 5:45 ahead of GMT |
Duration values use the prefix “P” for “period” as in <time datetime=“P4D”> (period = four days)…
So you start the attribute string value with a “P”, followed by a duration value that ends with another letter indicating the unit used: “D” for “days”, “H” for hours, “M” for minutes and “S” for seconds.
You can separate the different elements “P”, value and unit with spaces, but this is optional. So <time datetime="P4D"> is a duration of 4 days, as is <time datetime="P 4 D">.
Using a “T” after the “P” marker allows you to indicate a more accurate duration time: <time datetime=“PT4H 6M 12.55S”> is a duration of 4 hours, 6 minutes and 12.55 seconds.
Alternatively, you could use also a duration time component.
From Bruce Lawson’s article : “Whichever you choose, it’s represented internally as a number of seconds. Because of this, you can’t specify a duration in terms of months, because a month isn’t a precise number of seconds; a month can last from 28 to 31 days. Similarly, a year isn’t a precise number of seconds; it’s 12 months and February sometimes has an extra day.
You still can’t represent dates before the Christian era, as years can’t be negative. Neither can you indicate date ranges. To mark up From “21/02/2012 to 25/02/2012″, use two separate <time> elements.”
<h2>Recipe:</h2> <ul> <li> Preparation time: <time datetime="PT30M">30 minutes</time> </li> <li> Cooking time: <time datetime="PT10M">10 minutes</time> </li> </ul>
Used without attributes, the value between the opening <time> and closing <time> should follow the syntax given by the specification so that machines can understand it (same syntax as the one presented for the datetime attribute in the previous section). However it is recommended to use a datetime attribute, as it gives more freedom in the way you can display the date/time/duration in a human-readable form.
The HTML <mark> tag is used for indicating text as marked or highlighted for reference purposes, due to its relevance in another context.
<!DOCTYPE html> <html lang="en"> <head> <meta charset=utf-8 /> <title>JS Bin</title> </head> <body> <p>Project is due in <b><mark>.zip format</mark></b> next monday.</p> </body> </html>
<body> <pre> <code><mark>var</mark> i = 3;</code> </pre> <p>The var keyword is used to declare a variable in JavaScript.</p> </body>
If you don’t like the default yellow background, you may use CSS to change the style of the <mark> element:
mark { background-color: green; color: yellow; }
Everyone knows the classic way to make hyperlinks, using <a href="...">some text. What happens when you click on the hyperlink depends on the MIME type received by the browser. If you link to a file the browser knows how to render (an html page, a gif, jpg, or png image, etc.) there is a good chance that the MIME type received by the browser will be something like this:
Content-type: text/html, text/plain, image/gif, image/jpg, etc.
<a href="toto.jpg"> please right click this link to download the toto.jpg picture</a>
…will ask the remote HTTP server to send back the toto.jpg file. The browser will receive in the response HTTP header from the server (and by default the browser will display the image in a new tab):
1. ... 2. Content-type: image/jpg 3. ...
However, if the link points to some PHP code, Java servlet code, or any kind of script/application on the server side, this remote server code can send in its HTTP response a Content-type that may force the browser to download the image instead of rendering it.
It may also propose a name for the file to be downloaded that may be different from the one that appears in the URL of the href attribute. This can be done by generating, in addition to the Content-type line in the response HTTP header, a Content-Disposition line that looks like this:
Content-Disposition: attachment; filename="MyImage.png";
Here are some extracts from a Java Servlet that generate a zip file and forces the browser to propose downloading it using a specified name:
1. protected void doGet(HttpServletRequest request, HttpServletResponse response) 2. throws ServletException, IOException { 3. try { 4. // Build the zip file 5. String path = getServletContext().getRealPath("data"); 6. File directory = new File(path); 7. String[] files = directory.list(); 8. if (files != null && files.length > 0) { 9. byte[] zip = zipFiles(directory, files); 10. ServletOutputStream sos = response.getOutputStream(); 11. 12. // generate a HTTP response that forces the download 13. [response.setContentType("application/zip");] 14. [response.setHeader("Content-Disposition",] 15. ["attachment; filename="DATA.ZIP"");] 16. sos.write(zip); sos.flush(); 17. } 18. } catch (Exception e) { 19. e.printStackTrace(); 20. } 21. }
The above example will cause the browser that invoked this server-side code to start the download of a file named “DATA.ZIP”.
HTML5 proposes the use of a new attribute named download to download resources rather than navigating to them. The example below shows how to trigger the download of an image by the browser (instead of rendering it, which is the default behavior) with a name different from the name of the resource.
<a href="normal.gif" [download]=["MichelBuffa.gif"]> download a picture of Michel Buffa </a>
This will indeed force the download of an image with a filename different from its original filename on the server side. Here is a screen capture of the Web browser while downloading the picture. We can see in the status bar the name of the link (the image is “normal.gif”) and the downloaded file is “MichelBuffa.gif”:
WARNING: since 2015, and for security reasons, the image should be located on the same domain as the HTML page that contains the link (using a relative URL works well, for example, but linking a page on another domain will not work - it will keep its original name).
This demo shows the use of the download attribute together with the HTML5 File, FileSystem and FileWriter APIs (to be studied later in this course) for generating on-the-fly content from JavaScript code, and proposing downloading it to a file.
We won’t detail this demo here, but take a look if you are curious to see what can be done with this new download attribute. As the FileWriter and FileSystem APIs are still supported only by Google Chrome (other browsers need polyfills), you will need Google Chrome to try it.
We have also put the simplified source code of this demo on JSBin.com for you to play with.
HTML5 gives us a new translate attribute. This attribute is used to limit the impact of translation tools such as Google Translate by prohibiting the translation of certain content. In many cases some parts of a document should not be translated.
Both Google translate and Microsoft online translation services already offer the ability to prevent translation of content by adding markup to your content, although they do it in (multiple) different ways. Hopefully, the new attribute will help significantly by providing a standard approach.
The specification about the translate attribute tells us that “The translate attribute is an enumerated attribute that is used to specify whether an element’s attribute values and the values of its Text node children are to be translated when the page is localized, or whether to leave them unchanged.
The attribute’s keywords are the empty string, yes, and no. The empty string and the yes keyword map to the yes state. The no keyword maps to the no state. In addition, there is a third state, the inherit state, which is the missing value default (and the invalid value default).”
<span <b>[translate="no"]</b> class="author">[Michel Ham]</span>
In the above example, a <span> element defines an author (of a blog, for example) who is named Michel Ham. However, his family name is the same as pork and would be translated to “Michel Jambon” in French, or Michel Jamón in Spanish…
Using the translate=“no” attribute should prevent this behavior…
<span <b>[translate="no"]</b> class="author">[Michel Ham]</span> is a professor from the University of Nice, France.
Will be correctly translated into French by:
"[Michel Ham] est un professeur de l'Université de Nice, France."
…where all of the end of the sentence has been translated except the author’s name.
When you define an element as not being translatable, its children inherit this behavior and are themselves not translatable. The reverse is also true.
<p translate="no">This is a text in a paragraph element, that should not be translated: the p element has a translate="no" attribute.<span> This part that is in a span element embedded within the paragraph. It does not have a translate attribute but inherits the translation-mode of the p and will not be translated too</span>. This is the end of the paragraph...</p>
There are several ways to provide machine-readable content embedded in a classical Web document: HTML+RDFa, microformats, JSON-LD, HTML5 microdata. In this section, we focus on microdata.
Adding microdata to Web pages helps search engines to better understand the pages’ content, their topics, etc. The main purpose of microdata is Search Engine Optimization(SEO).
This information is not visible to humans: it is pure semantic information. Popular kinds of microdata are events, a person’s profile, the description of an organization, the details of a recipe, a product description, a geographical location, etc.
1. <section [itemscope itemtype="https://schema.org/Person"]> 2. <h1>Contact Information</h1> 3. <dl> 4. <dt>Name</dt> 5. <dd [itemprop="name"]>Michel Buffa</dd> 6. <dt>Position</dt> 7. <dd><span [itemprop="jobTitle"]> 8. Professor/Researcher/Scientist</span> for 9. <span [itemprop="affiliation"]> 10. University of Côte d'Azur, France 11. </span> 12. </dd> 13. </dl> 14. <!-- SURFACE ADDRESS GOES HERE --> 15. <h1>My different online public accounts</h1> 16. <ul> 17. <li><a href="https://www.twitter.com/micbuffa" 18. [itemprop="url"]>Twitter profile</a></li> 19. <li><a href="https://www.blogger.com/micbuffa" 20. [itemprop="url"]>Michel Buffa's blog</a></li> 21. </ul> 22. </section>
We can also add another embedded data item in the middle, such as the person’s address:
1. ... 2. </dl> 3. 4. <!-- SURFACE ADDRESS GOES HERE --> 5. 6. <dd [itemprop=]"address"[ itemscope] 7. itemtype="https://schema.org/PostalAddress"> 8. <span [itemprop=]"streetAddress">10 promenade des anglais</span><br> 9. <span [itemprop=]"addressLocality">Nice</span>, 10. <span [itemprop=]"addressRegion">Alpes maritimes, France</span> 11. <span [itemprop=]"postalCode">06410</span><br> 12. <span [itemprop=]"addressCountry"[ itemscope] 13. [itemtype=]"https://schema.org/Country"> 14. <span [itemprop=]"name">France</span> 15. </span> 16. </dd> 17. 18. <h1>My different online public accounts</h1> 19. 20. ...
In the following sections, we look more closely at the itemprop, itemscope and itemtype attributes.
Note: For advanced users, Microdata is very similar to microformats, which use HTML classes, or to RDFa, which doesn’t validate in HTML4 or HTML5. Because RDFa was considered to be too hard for authors to write, microdata is HTML5’s answer to help embed semantics into html documents.
After seeing the principle of embedding microdata in an HTML page, we now present some structured data test tools you can use to check if your data are correct.
One of the most popular resources for testing microdata (as well as microformats and RDFa) is this Google page about understanding how structured data works. This page contains a link to a structured data testing tool that you can use to see how Google recognizes the semantic data you embed in your HTML code.
Let’s have a look now at a (small) example of an about page. It renders as a very simple paragraph that explains who Michel Buffa is… But we embedded Microdata, so it’s interesting to see how a search engine sees it, and how it may produce “augmented search results”.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset=utf-8 /> 5. <title>Michel Buffa</title> 6. </head> 7. <body> 8. <div [itemscope itemtype="https://schema.org/Person"]> 9. My name is <span itemprop="name">Michel Buffa</span>, 10. And I'm a <span itemprop="jobTitle">professor/researcher</span> at 11. <a href="https://www.i3s.unice.fr/" itemprop="affiliation">I3S 12. Laboratory</a> in the south of France, near the city of Nice. My 13. email 14. is : <span itemprop="email">[email protected]</span>. 15. I live in the city of 16. <span itemprop="address" itemscope 17. [itemtype="https://schema.org/PostalAddress"]> 18. <span itemprop="addressLocality">Biot</span>, in a region named 19. <span itemprop="addressRegion">Alpes Maritimes</span> 20. </span> 21. </div> 22. </body> 23. </html>
Here is what Google sees of the page. We just entered its URL in the Google page about rich snippets and structured data:
Note that the address is a fully featured embedded object in the Person’s description.
The Live Microdata Web site is a bit similar to the previous one except that it shows the extracted metadata as JSON objects:
Adding microdata to an HTML page is a really simple task and requires only three attributes: itemscope, itemtype and itemprop.
First, you need to add an itemscope attribute to an HTML element. This will define the “global object” for which we will define properties. This element can be of different types that we will describe later, but for now let us keep looking at the same example we used in previous sections:
<section itemscope itemtype="https://schema.org/Person"> ... </section>
We will look at the itemtype attribute later. Now that we have defined a global wrapper object/element (a Person in this case), we can add properties inside this element to define the first name, last name, etc.
HTML5 proposes semantic elements for representing sections, articles, headers, etc, but it does not propose any specific elements or attributes to describe an address, a product, a person, etc.
We need a special vocabulary to represent a person or a physical address. With microdata you can define your own vocabulary or better, reuse one of the existing popular vocabularies, such as schema.org.
Microdata works with properties defined as name/value pairs. The names are defined in the corresponding vocabulary. For example, the vocabulary for representing a Person defines a set of property names.
As you can see in this small extract from the vocabulary (also called a “schema”), a Person can have a name (some text), an Address (the type is defined by another vocabulary named PostalAddress), an affiliation (defined by another vocabulary named Organization) and so on.
We notice that one property, such as the address of a Person, may use another vocabulary. Yes, a vocabulary may link to another vocabulary! There is also inheritance between vocabularies! The above screenshot shows that the Person vocabulary inherits from a Thing vocabulary, and the five first properties of the table come from this vocabulary that describes things.
If you are a developer and if you are familiar with object oriented programming, think of properties as class attributes and think of vocabularies as classes.
If one of the existing vocabularies available at the schema.org Web site fits your needs, you should reuse it, as the most popular vocabularies are becoming de facto standards and will be taken into account by Web crawlers, browsers, and browser extensions.
However, if you do not find a vocabulary corresponding to your needs, keep in mind that anyone can define a microdata vocabulary and start embedding custom properties in their own Web pages. You need to define a namespace and put a description of your vocabulary in a Web page that has the name of your vocabulary.
Now that you have defined a container element, you may add properties to the HTML inside:
1. <section itemscope itemtype="https://schema.org/Person"> 2. <h1>Contact Information</h1> 3. <dl> 4. <dt>Name</dt> 5. <dd [itemprop="name"]>Michel Buffa</dd> 6. <dt>Position</dt> 7. <dd><span [itemprop="jobTitle"]> 8. Professor/Researcher/Scientist 9. </span> for 10. <span [itemprop="affiliation"]>University of Nice, 11. France 12. </span> 13. </dd> 14. </dl> 15. <h1>My different online public accounts</h1> 16. <ul> 17. <li><a href="https://www.twitter.com/micbuffa" 18. [itemprop="url"]>Twitter profile</a></li> 19. <li><a href="https://www.blogger.com/micbuffa" 20. [itemprop="url"]>Michel Buffa's blog</a></li> 21. </ul> 22. </section>
In this example, the container is a <section> that corresponds to a Person (we have one clue here: the name of the vocabulary given by the itemtype attribute), and each property defined inside this section is identified by the value of the itemprop attribute of sub-elements.
<dd itemprop="name"Michel Buffa</dd>
…defines a property called “name” that has a value of “Michel Buffa” (the text value between the opening and closing tags of the <dd> element).
As we saw with the Person/Address example at the beginning of this
chapter, it is possible to nest microdata items inside one another.
Give an element inside a microdata container its own itemscope attribute
with the recommended itemtype attribute for indicating the name of the
vocabulary used by the nested microdata.
1. ... 2. </dl> 3. 4. <!-- SURFACE ADDRESS GOES HERE --> 5. 6. <dd itemprop="address"[ itemscope] 7. [itemtype="https://schema.org/PostalAddress"]> 8. <span itemprop="streetAddress">10 promenade des anglais</span><br> 9. <span itemprop="addressLocality">Nice</span>, 10. <span itemprop="addressRegion">Alpes maritimes, France</span> 11. <span itemprop="postalCode">06410</span><br> 12. <span itemprop="addressCountry" itemscope 13. [itemtype="https://schema.org/Country"]> 14. <span itemprop="name">France</span> 15. </span> 16. </dd> 17. 18. <h1>My different online public accounts</h1> 19. 20. ...
The properties at lines 8-12 refer to the address nested microdata (they are defined in the Address vocabulary, not the Person vocabulary), and “France” (line 14) is a property that refers to the Country vocabulary.
It is possible to use the same property name several times in one microdata object, but with different values:
1. ... 2. <h1>My different online public accounts</h1> 3. <ul> 4. <li><a href="https://www.twitter.com/micbuffa"[ itemprop="url"]>Twitter 5. profile</a></li> 6. <li><a href="https://www.blogger.com/micbuffa"[ itemprop="url"]>Michel 7. Buffa's blog</a></li> 8. </ul>
This defines the fact that Michel Buffa has two online accounts, and the two properties have the name url, each with its own value.
Here are some microdata that represent a song. In this example, at line 5 we set two different properties: genre and keywords with the same value (see the MusicRecording schema definition):
1. <div itemscope itemtype="https://schema.org/MusicRecording"> 2. <h2>The song I just published</h2> 3. <ul> 4. <li>Name: <span itemprop="name">Please buy me on itunes, I need money!</span></li> 5. <li>Band: <span [itemprop="genre keywords"]>[Punk, Ska]</span></li> 6. </ul> 7. </div>
And so on…
Now, let’s see what elements are compatible with the itemprop attribute
and where the values of the properties are located, depending on each
element type.
If the itemprop attribute appears on a:
HTML5 elements | microdata value associated |
---|---|
<a>, <area>, <audio>, <embed>, <iframe>, <img>, <link>, <object>, <source>, or <video> element | The data is the url in the element’s href, src, or data attribute, as appropriate. For example, an image element inside a container of personal contact information can be recognized as that person’s photo and downloaded accordingly. |
<time> element | The data is the time in the element’s datetime attribute. This lets you, for example, just say “last week” in your text content but still indicate exact date and time. |
<meta> element | The data is whatever appears in the content attribute of the <meta> element. This is used when you need to include some data that isn’t actually in the text of your page. |
anything else | The data is whatever is in the text of the element. |
For example, the value of a property defined in an <img> element will be the value of the src attribute:
<img itemprop="image" src="MichelBuffa.png" alt="A great professor">
Or, for a <time>, it will be the value of the datetime attribute:
<time itemprop="birthday" datetime="1965-04-16">April 16, 1965</time>
Or, for an <a> element, the value will be the value of the href attribute:
<a href="https://www.twitter.com/micbuffa" itemprop="url">profile</a>
There are many tools available (most are free) that you can use for generating, visualizing and debugging microdata. We list some of them in this page, but feel free to share the tools you find / like in the forums.
To automatically generate microdata for describing persons, restaurants, movies, products, organizations, etc., there is a wide variety of microdata generators such as these listed below (but do not hesitate to search for “microdata generators” using your favorite search engine, and you will find lots!):
Here, we propose a few links to Web pages that were created by students of previous editions of this course).
The students had to create a Web page to introduce themselves, with some information including: name, job, employer, location, etc., and of course enrich the page with microdata. They also had to follow the best practices concerning the new structural elements, headings, etc.
Click on these pages and look at the source code…
Visit the example #1 online.
View the example #2 online.
Until 2012, it was only possible to integrate an audio or video player using the proprietary Flash technology, marketed by the company Macromedia (later acquired by Adobe). The <video> element of HTML5 is one of the three “Flash killers” (the others being <audio> for the sound and <canvas> for drawing and animation). (Note that Adobe no longer supports Flash Player since December 31, 2020)
You will learn more about the different attributes of the <video> element later on in the course.
The <video> element is supported by all major browsers. See the support table from CanIUse.
Help! <video src=“my youtube video URL”></video> does not work!
BEWARE: you cannot directly embed videos from most of the popular Web sites such as YouTube, Dailymotion, Vimeo, etc. For commercial reasons, and because advertising is automatically added to the videos, these Web sites do not allow “regular” embedding of their videos.
While they use HTML5 to render their videos, these hosting sites (YouTube, etc.) use rather complex techniques in order to prevent you from using them with the <video>element. Instead, you often need to embed an <iframe> that will render the HTML5 videos in your Web site, and of course, the advertising that comes along with them.
Usually you have an “embed” button close to the videos that prompts you with some HTML code that you can copy and paste for embedding.
<iframe width="560" height="315" src="https://www.youtube.com/embed/WMFXg-kni0U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
The YouTube video embedded in this page by the above code: it’s HTML5 but it’s not a <video> element directly inserted in the HTML of this page, it’s an <iframe>.
This is one of the main problems encountered in recent years: codec support was not the same from one browser to another, for commercial/economic reasons. For example, between 2010 and 2013, Firefox only supported the ogg/oggm format. It did not support mp3/mp4 encoding for audio/video, while Internet Explorer only supported H.264 encoding. Since 2012, things have changed with browser updates and today most popular formats are supported.
The recommended CODEC, which works in all popular browsers: H264/mp4.
This course will focus on the <audio> element. We present the Web Audio API and other advanced HTML5 features in the W3Cx HTML5 Apps and Games course.
The attributes, event set and JavaScript API of the <audio> element are just a “reduced” version of the ones from the <video> element, and here we will only address the differences and peculiarities.
Here is a simple example (also available online example from JSBin):
Press play to stream the neigh of a horse: (does not work in git)
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>horse song</title> 6. 7. </head> 8. <body> 9. <audio controls="controls"> 10. <source src="https://mainline.i3s.unice.fr/mooc/horse.ogg" type="audio/ogg" /> 11. <source src="https://mainline.i3s.unice.fr/mooc/horse.mp3" type="audio/mp3" /> 12. Your browser does not support the audio element. 13. Download the audio/video in 14. <a href="https://mainline.i3s.unice.fr/mooc/horse.ogg">OGG</a> 15. or <a href="https://mainline.i3s.unice.fr/mooc/horse.mp3">MP3</a> 16. format. 17. </audio> 18. 19. </body> 20. </html>
In this example, just as for the <video> element, we used the controls attribute in order to render the play/stop, time, volume and progress widgets.
Notice the other similarities: between the <audio>…<audio> tags, we added a text message that is displayed if the Web browser doesn’t support the <audio> element, and we used several <source>…</source> elements that link to different audio formats for the same file. The browser will use the first format it recognizes.
Here are the most common attributes you can use with the <video> element. They are self explanatory…
The autoplay attribute is not recommended if your Web site targets mobile applications (actually, it is often ignored by mobile browsers), as it may consume bandwidth even if the user is not interested in watching the proposed video. If you target mobile devices, we recommend using preload=none as well, as the default value for this attribute is auto.
Best practice: do not use autoplay and add preload=“none” if you target mobile devices or if you have multiple audio/video files on the same page. For example, this page contains many audio elements and it does not make sense to have them preload or autoplay.
If the poster attribute is missing, usually the first non-blank frame of the video will be used as the image that is shown when the video is not playing.
Do not abuse of the autoplay attribute. We talked earlier about mobile applications, but even on desktop applications it’s usually a bad idea to use it (except for WebCams and for some animations with small video loops, without sound, or for sites like YouTube, with just videos).
Best practice: think twice before using the autoplay attribute, even for desktop applications.
The attributes you can use with the <audio> element are a subset of those available for the <video> element. Except for the poster attribute, they are all recognized and have the expected meanings:
As with the <video> element, the same best practice in regard to preload and autoplay attributes should be followed.
The <video> and <audio> elements are just like other HTML elements, so CSS can be used for styling, including CSS transitions, animations, etc.
You can try this example online at JSBin.
To add some styling to the basic example we saw when we introduced the <audio> element, we just add a <figure> with two children: an <img> and a <figcaption>. Inside the <figcaption> we add the <audio> element from the previous example.
Please move the mouse pointer over this player’s elements: (does not work in git)
1. <figure id="figaudio1"> 2. <img id="imghorse" width="200" 3. src="https://upload.wikimedia.org/wikipedia/commons/d/d4/Nokota_Horses.jpg" 4. alt = "a horse"/> 5. <figcaption id="figcptionaudio1"> Press Play to hear the horse! 6. <audio controls="controls"> 7. <source src="https://mainline.i3s.unice.fr/mooc/horse.ogg" 8. type="audio/ogg" /> 9. <source src="https://mainline.i3s.unice.fr/mooc/horse.mp3" 10. type="audio/mp3" /> 11. Your browser does not support the audio element. 12. Download the audio/video in 13. <a href="https://mainline.i3s.unice.fr/mooc/horse.ogg">OGG</a> 14. or <a href="https://mainline.i3s.unice.fr/mooc/horse.mp3">MP3</a> 15. format. 16. </audio> 17. </figcaption> 18. </figure>
Press Play to hear the horse! (does not work in git)
1. <figure id="figaudio1"> 2. <img id="imghorse" width="200" 3. src="https://upload.wikimedia.org/wikipedia/commons/d/d4/Nokota_Horses.jpg" 4. alt = "a horse"/> 5. <figcaption id="figcptionaudio1"> Press Play to hear the horse! 6. <audio controls="controls"> 7. <source src="https://mainline.i3s.unice.fr/mooc/horse.ogg" 8. type="audio/ogg" /> 9. <source src="https://mainline.i3s.unice.fr/mooc/horse.mp3" 10. type="audio/mp3" /> 11. Your browser does not support the audio element. 12. Download the audio/video in 13. <a href="https://mainline.i3s.unice.fr/mooc/horse.ogg">OGG</a> 14. or <a href="https://mainline.i3s.unice.fr/mooc/horse.mp3">MP3</a> 15. format. 16. </audio> 17. </figcaption> 18. </figure>
1. #figaudio1 { 2. width : 420px;; 3. text-align:center; 4. padding : 6px; 5. background : white; 6. margin : 0 11px 0px 0; 7. border :solid 1px #888888; 8. border-radius : 8px ; 9. } 10. 11. #figcptionaudio1 { 12. font-size : .8em; 13. padding : 6px 8px; 14. background : #dddddd; 15. display :block; 16. text-align :center; 17. font-family : georgia, serif; 18. font-style : italic; 19. border-radius : 7px ; 20. } 21. 22. #figaudio1 > img { 23. background : #eeeeee; 24. padding : 5px; 25. border : solid 1px #444444; 26. } 27. 28. /* For audio and img transitions/animation */ 29. audio, #figaudio1 > img { 30. transition:all 0.5s; 31. } 32. 33. #figaudio1 > img:hover { 34. box-shadow: 15px 15px 20px rgba(0,0, 0, 0.4); 35. transform: scale(1.05); 36. } 37. 38. audio:hover, audio:focus, audio:active { 39. box-shadow: 15px 15px 20px rgba(0,0, 0, 0.4); 40. transform: scale(1.05); 41. }
See this example online (where you can modify the code on the fly) or just play the following video, and move the mouse pointer in and out of the video while it’s playing.
This example uses the pseudo CSS class :hover in order to track the mouseover event. On mouseover, it uses a CSS transition property that interpolates the changes in the scale and orientation of the video element (done using a transform CSS property).
1. <video <b>id="w3devCampusVideo"</b> autoplay controls> 2. 3. <source src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.webm 4. type=video/webm> 5. <source src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.ogg 6. type=video/ogg> 7. <source src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.mp4 8. type=video/mp4> 9. </video>
1. #w3devCampusVideo { 2. width: 300px; 3. <b>transition: all 0.5s ease-in-out;</b> 4. } 5. 6. #w3devCampusVideo</b>:hover</b> { 7. width:400px; 8. transform:rotate(-5deg); 9. }
This is a trendy way of displaying videos.
Below you will find two examples that show how to do this trick. The first is for a “regular” video, using the <video> and <source> elements. This technique can also be used on any YouTube embedded videos (see Example #2 below).
The interesting part is that we use a 100% standard (and really small and simple) JavaScript code here to handle the window resize events and we just set regular CSS properties width and height of the video element, to resize the video.
Full width, resizable, borderless video, just using plain CSS and JS DOM events.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Full width video like PayPal site</title> 6. </head> 7. <body onload="init();"> 8. <video id="myVideo" autoplay> 9. <source 10. src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.webm 11. type=video/webm> 12. <source 13. src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.ogg 14. type=video/ogg> 15. <source 16. src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.mp4 17. type=video/mp4> 18. </video> 19. </body>
body { margin:0; padding:0; overflow:hidden; }
1. var video; 2. function init() { 3. // function called when the page is loaded 4. video = document.querySelector("#myVideo"); 5. // For initial value 6. video.width = window.innerWidth; 7. video.height = window.innerHeight; 8. // For dealing with window resize 9. window.onresize = function() { 10. video.width = window.innerWidth; 11. video.height = window.innerHeight; 12. }; 13. }
Full width, resizable, borderless YouTube video. To do this: just 100% standard CSS + DOM manipulation using JavaScript.
The CSS and JavaScript codes for this example are exactly the same as in Example #1.
In this example, the video does not rescale; it’s just cropped if the browser window is resized. Enlarge your browser and you’ll see a man with a phone on the right. Resize your browser and you’ll see only part of the video.
body { margin:0; padding:0; overflow:hidden; } video { width:100%; height:auto; }
This time the video is zoomed in so that it’s much bigger than the browser’s window. When we resize the browser, the part of the video that is visible adapts itself. It’s not “real resize” of the video. Try this example and read the explanation in this article by Dudley Storey.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Full screen video with CSS effects </title> 6. </head> 7. <body> 8. <header> 9. <video autoplay loop="" 10. poster="https://mainline.i3s.unice.fr/mooc/polina.jpg" 11. id="bgvid"> 12. <source src="https://mainline.i3s.unice.fr/mooc/polina.webm" 13. type="video/webm"> 14. <source src="https://mainline.i3s.unice.fr/mooc/polina.mp4" 15. type="video/mp4"> 16. </video> 17. </header> 18. <section> 19. <h1>Full screen video with CSS effects</h1> 20. </section> 21. </body> 22. </html>
1. html, body{ 2. color:white; 3. height: 100%; 4. } 5. header{ 6. height: 100%; 7. background-image: url('https://mainline.i3s.unice.fr/mooc/dots.png'), url('#'); 8. background-repeat: repeat, no-repeat; 9. background-size: auto, cover; 10. background-position: center center, top left; 11. font-family: sans-serif; 12. color: #051a00; 13. } 14. header video { 15. position:fixed; 16. top:50%; 17. left:50%; 18. min-width:100%; 19. min-height:100%; 20. width:auto; 21. height:auto; 22. z-index:-100; 23. transform:translateX(-50%) translateY(-50%); 24. }
the video is in the header, and the header has a plotted transparent background image (“dots.png”) that is repeated in X and Y (see lines 8 and 9).
Full screen video that resizes and keeps its ratio, using the viewport units.
This time we obtain the same result as with the first example that used JavaScript and a resize event. The video resizes correctly and keeps its ratio.
1. body { 2. margin: 0; 3. } 4. 5. video { 6. position: absolute; 7. width: 100vw; 8. height: 100vh; 9. object-fit: cover; 10. object-position: center center; 11. }
Let’s use the same video to compare the different approaches again:
Resizing the browser window shows that #1 (JavaScript) and #3 (viewport units) behave in the same way: the width or height of the video always fills the window (whichever is smaller), and we always see the whole video.
Conclusion: we can get full size video without JavaScript by using viewport units (vw and vh), unless we need to support some old browsers (see their current support on CanIUse).
Setting the video to 100% width and height results in different behavior:
The <video> and <audio> elements have methods, properties/attributes and events that can be manipulated with JavaScript. Using the DOM API, it’s possible to manipulate an audio or video element as a JavaScript object that has:
The set of properties/attributes/methods of the <audio> and <video> elements is called an “API” (Application Programming Interface). For example, we will speak here of the “media API” to talk about the associated API.
Like any HTML element, the <video> element can be manipulated/created using the DOM JavaScript API. Here is an example of programmatically creating a <video> element:
1. var video = document.createElement('video'); 2. video.src = 'video.mp4'; 3. video.controls = true; 4. document.body.appendChild(video);
This will create a complete video player for the file “video.mp4”, with control buttons, and will add it to the <body> element of the page.
Please look at this interesting example:
Note that in order to play the video, you must click on the “vid.play()” text. To pause it, you click on the “vid.pause()” text, and so on. Notice the text at the top of the video, as well as the transparency. The text can be selected, since all the elements displayed are pure DOM objects. You can zoom the page in and out, etc. This was not possible with the Flash technology.
Conclusion: you can very easily change the look and feel of the standard video player by using custom CSS and designing your own control widgets. We can find many examples of such video players that offer extended functionalities on the Web. We will present some of them later in the course, but before that, let’s see a little more of what we can do using the JavaScript API of the <video> element (it will be an identical approach for the <audio> element, since it shares the same API by a very small margin).
The JavaScript API gives you powerful tools to manipulate the <video> element, as the video object provides many properties, methods and events.
The complete list of events can be found in the HTML5 living standard specification.
The list of properties can be found at the W3C HTML5 Video Events and API page. This page is interesting for Web developers because it shows an interactive view of the different values and events changing over time while the video is playing within the page.
Try the direct link, and play with the different buttons and look at the table of events and properties that will change in real time. The displayed names show the properties, events, and methods from the API.
We provide this as a quick reminder - keep in mind that the complete list is much longer!
Methods | Properties | Events |
---|---|---|
play() | currentSrc | play |
pause() | currentTime | pause |
load() | startTime (readonly) | progress |
canPlayType() | videoWidth | error |
videoHeight | timeupdate | |
duration (readonly) | ended | |
ended (readonly) | abort | |
error | empty | |
paused (readonly) | emptied | |
muted | waiting | |
seeking | loadedmetadata | |
volume | ||
height | ||
width | ||
seekable (readonly) | ||
played (readonly) |
In the next pages, let’s see, through a set of examples, how to use these most important properties, methods, and events…
The JavaScript API is useful for implementing playlists, making custom user interfaces and many other interesting things. The “enhanced HTML5 multimedia players” lesson presented further on the course relies heavily on this API.
This example gives the first steps towards writing a custom video player. It shows basic usage of the JavaScript API for adding custom buttons to play/pause the video or to go back to the beginning by setting the currentTime property to zero.
1. <video id="vid" controls> 2. <source src=https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.webm 3. type=video/webm> 4. </video> 5. <p>Example of custom controls:</p> 6. <button onclick="playVideo();" style="cursor: pointer;">Play</button> 7. <button onclick="pauseVideo();" style="cursor: pointer;">Pause</button> 8. <button onclick="rewindVideo();" style="cursor: pointer;"> 9. Back to beginning</button> 10. <script> 11. vid = document.querySelector("#vid"); 12. function playVideo() { 13. vid.play(); 14. } 15. function pauseVideo() { 16. vid.pause(); 17. } 18. function rewindVideo() { 19. vid.currentTime = 0; 20. } 21. </script>
This example listens to the ended event, and calls a callback function when the video is ended.
1. <video src="video.ogv" id="myVideo"> 2. video not supported 3. </video> 4. <script type='text/javascript'> 5. var vid = document.querySelector('#myVideo'); 6. vid.addEventListener('ended', playNextVideo, false); 7. function playNextVideo(e) { 8. // Whatever you want to do after the event, change the src attribute 9. // of the video element, for example, in order to play another video 10. } 11. </script>
This example detects the end of a video, then loads the next video, changes the src attribute of the video element and plays the video.
Check the online example below: use the progress cursor to go near the end of the first video that is being played, and see how it continues with the next video.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Sequential Movies</title> 6. <script> 7. var myVideo; 8. var currentVideo = 0; 9. var sources = [ 10. "https://mainline.i3s.unice.fr/mooc/samuraiPizzacat.mp4", 12. "https://www.archive.org/download/AnimatedMechanicalArtPiecesAtMit/P1120973_512kb.mp4" 12. ]; 13. // Set the src of the video to the next URL in the playlist 14. // If at the end we start again from beginning (the modulo 15. // source.length does that) 16. function loadNextVideo() { 17. myVideo.src = sources[currentVideo % sources.length] 18. myVideo.load(); 19. currentVideo++; 20. } 21. // listener plays the video 22. function loadAndplayNextVideo() { 23. console.log("playing " + sources[currentVideo % sources.length]) 24. loadNextVideo(); 25. myVideo.play(); 26. } 27. // Called when the page is loaded 28. function init(){ 29. // get the video element using the DOM api 30. myVideo = document.querySelector("#myVideo"); 31. // Defines a callback function called each time a video ended 32. myVideo.addEventListener('ended', loadAndplayNextVideo, false); 33. // Loads the first video when the page is loaded 34. loadNextVideo(); 35. } 36. </script> 37. </head> 38. <body onload="init()"> 39. <video id="myVideo" 40. controls> 41. </video> 42. </body> 43. </html>
In this section, we propose five extended examples that use more JavaScript and more complex CSS manipulation. They might be a little hard to understand if you are a JavaScript beginner, but don’t be afraid to try and test them, look at the code, etc.
Some examples are given “as is”, such as the custom video player that uses SVG (at the end of the page); if you are interested, you may view the code.
Please see this example online, originally written by Chris Heilmann, and tuned by us ;).
Don’t forget to click the JavaScript and CSS tabs of the CodePen in order to display the JavaScript code that creates the buttons on the right of the video, and the CSS that processes the different clicks and applies CSS3 transforms.
This example also shows how to handle failures. See the code and play with this example below:
Below is a piece of code for handling errors during video playback:
1. ... 2. vid.addEventListener('error', function(evt) { 3. logEvent(evt,'red'); 4. }, false); 5. ... 6. function logEvent(evt, color) { 7. switch (evt.type) { 8. ... 9. case 'error': 10. var error = document.querySelector('video').error; 11. switch (error.code) { 12. case error.MEDIA_ERR_ABORTED: 13. note.innerHTML = "fetching aborted at the user's request"; 14. break; 15. case error.MEDIA_ERR_NETWORK: 16. note.innerHTML = "a network error caused the browser to stop fetching the media"; 17. break; 18. case error.MEDIA_ERR_DECODE: 19. note.innerHTML = "an error occurred while decoding the media"; 20. break; 21. case error.MEDIA_ERR_SRC_NOT_SUPPORTED: 22. note.innerHTML = "the media indicated by the src 23. attribute was not suitable"; 24. break; 25. default: 26. note.innerHTML = "an error occurred"; 27. break; 28. } 29. break; 30. } 31. ... 32. }
See the JSBin example online here too.
Note that on mobile phones, the video does not start until the user presses the play control or clicks on the video picture. Using the "canplaythrough" event is a trick to call a function that starts the video player as soon as the page is loaded on desktop. This event is not supported by mobile devices, so if you try this example on a mobile, the video will not start automatically.
As explained by the Apple Developer Web site: "The buffered property is a TimeRanges object: an array of start and stop times, not a single value. Consider what happens if the person watching the media uses the time scrubber to jump forward to a point in the movie that hasn’t loaded yet—the movie stops loading and jumps forward to the new point in time, then starts buffering again from there. So the buffered property can contain an array of discontinuous ranges. The example simply seeks the end of the array and reads the last value, so it actually shows the percentage into the movie duration for which there is data."
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <title>JavaScript Progress Monitor</title> 5. <meta charset="utf-8"/> 6. <script> 7. function getPercentProg() { 8. var myVideo = document.getElementsByTagName('video')[0]; 9. var endBuf = myVideo.buffered.end(0); 10. var soFar = parseInt(((endBuf / myVideo.duration) * 100)); 11. document.getElementById("loadStatus").innerHTML = soFar + '%'; 12. } 13. // Will be called as soon as the page is ready on desktop computer, 14. // Only when a user clicks on play control or image on mobile 15. function myAutoPlay() { 16. var myVideo = document.getElementsByTagName('video')[0]; 17. myVideo.play(); 18. } 19. function addMyListeners(){ 20. var myVideo = document.getElementsByTagName('video')[0]; 21. myVideo.addEventListener('progress', getPercentProg, false); 22. // Calls autoplay only if the device is adapted 23. myVideo.addEventListener('canplaythrough', myAutoPlay, false); 24. } 25. </script> 26. </head> 27. <body onload="addMyListeners()"> 28. <h1>Check progression of buffering before playing a movie. Useful withy 29. slow connexion (3G, etc.)</h1> 30. <div> 31. <video controls> 32. <source src=https://html5doctor.com/demos/video-canvas-magic/video.webm 33. type=video/webm> 34. <source src=https://html5doctor.com/demos/video-canvas-magic/video.ogg 35. type=video/ogg> 36. <source src=https://html5doctor.com/demos/video-canvas-magic/video.mp4 37. type=video/mp4> 38. </video> 39. <p id="loadStatus">Buffering...</p> 40. </div> 41. </body> 42. </html>
This is the ultimate way of doing a real custom player: redesign your own controls using SVG shapes! This example (try it online) is given “as is” for those of you who may be curious.
This is more an example than a tutorial. Maurice, a student who followed the precursor version of this MOOC, had the assignment to write a custom video player with playlist, video thumbnails, custom play/pause/next/previous/volume controls, and present it in a Web page that used a nice layout based on the HTML5 structuring elements studied previously.
Here is the online example. We recommend that you look at the source code:
This section introduces the HTML5 <track> element, useful for adding closed captions, subtitles, descriptions, and metadata to your videos. It comes with a new JavaScript API.
The WebVTT format used for describing a track file is also presented in this chapter.
Please check the browser support related to the <track> element support by browsers.
The accessibility features of TV programs often propose both options for people with hearing deficiencies.
The <track> element cannot be used with a file:// URL. Please use https:// and a Web server. Your server must use a special MIME format for the .vtt files: text/vtt;charset=utf-8 (set by default on most servers now).
1. <Files mysubtitle.vtt> 2. ForceType text/vtt;charset=utf-8 3. </Files>
It is worth mentioning that most browsers work well with WebVTT, even if the MIME type is not defined.
Here is an example of a video element that includes a <track> element in the .vtt (WebVTT) format (line 9 in the source code shown below):
The example uses a <track> element to insert basic captions to the video: sounds and music are described, in addition to standard subtitles that correspond to what the different movie characters say.
<video height="272" width="640" poster="https://mainline.i3s.unice.fr/mooc/q1fx20VZ-640.jpg" crossorigin="anonymous" controls> <source src="https://mainline.i3s.unice.fr/mooc/sintel.mp4" type="video/mp4"> <source src="https://mainline.i3s.unice.fr/mooc/sintel.webm" type="video/webm"> <track src="https://mainline.i3s.unice.fr/mooc/sintel-captions.vtt" kind="captions" label="Closed Captions" default> </video>
Multiple tracks are needed to support different langages, video captions for the hearing-impaired, subtitles, etc.
Below is an example (from the specification) that includes multiple <track> elements (subtitles for three languages and captions only for English):
1. <video src="brave.webm"> 2. <track kind=subtitles src=brave.en.vtt 3. srclang=en 4. label="English"> 5. <track kind=captions src=brave.en.hoh.vtt 6. srclang=en 7. label="English for the Hard of Hearing"> 8. <track kind=subtitles src=brave.fr.vtt 9. srclang=fr 10. lang=fr 11. label="Français"> 12. <track kind=subtitles src=brave.de.vtt 13. srclang=de 14. lang=de 15. label="Deutsch"> 16. </video>
Note the use of some new attributes in the <track> element:
The " ;WebVTT: The Web Video Text Tracks Format " defines files that contain text for captions and subtitles, and much more… The WebVTT files are used with the src attribute of the <track> element, that can be used inside a <video>…</video>.
In the interactive example presented before, we used a file called sintel-captions.vtt:
<video height="272" width="640" poster="https://mainline.i3s.unice.fr/mooc/q1fx20VZ-640.jpg" crossorigin="anonymous" controls> ... <track src="https://mainline.i3s.unice.fr/mooc/sintel-captions.vtt" kind="captions" label="Closed Captions" default> </video>
And here is an extract of the corresponding sintel-captions.vtt file:
1. WEBVTT 2. 3. 00:00:01.000 --> 00:00:02.042 4. (drumbeat) 5. 6. 00:00:07.167 --> 00:00:12.025 7. (plaintive violin solo playing) 8. 9. 00:00:15.000 --> 00:00:18.183 10. (wind whistling) 11. 12. 00:00:24.167 --> 00:00:27.025 13. (orchestra music swells) 14. 15. 00:00:43.033 --> 00:00:43.192 16. (weapons clash) 17. 18. 00:00:44.000 --> 00:00:44.175 19. (gasps) 20. 21. 00:00:44.183 --> 00:00:45.158 22. (grunts) 23. 24. 00:00:45.167 --> 00:00:47.058 25. (groaning) 26. 27. 00:00:54.192 --> 00:00:55.150 28. (blade rings) 29. 30. 00:00:55.158 --> 00:00:57.008 31. (bellowing) 32. 33. 00:00:57.017 --> 00:00:58.067 34. (grunting) 35. 36. 00:00:59.075 --> 00:01:00.133 37. (panting) 38. 39. 00:01:05.108 --> 00:01:06.125 40. (cries out in agony) 41. 42. 00:01:08.050 --> 00:01:09.058 43. (panting) 44. 45. 00:01:12.092 --> 00:01:13.142 46. (panting) 47. 48. 00:01:14.017 --> 00:01:18.125 49. (orchestra plays ominous low notes) 50. 51. 00:01:31.058 --> 00:01:35.133 52. (plaintive violin solo returns) 53. 54. 00:01:46.158 --> 00:01:49.058 55. This blade has a dark past. 56. 57. 00:01:51.092 --> 00:01:54.108 58. It has shed much innocent blood. 59. 60. 00:01:57.083 --> 00:02:00.000 61. You're a fool for traveling alone 62. so completely unprepared. 63. 64. 00:02:01.100 --> 00:02:03.033 65. You're lucky your blood's still flowing. 66. 67. 00:02:04.183 --> 00:02:06.075 68. Thank you.
This format is rather simple, but we still recommend reading this excellent article from Mozilla Developer Network that explains in detail all the different options.
Each “element” in this file has a starting and ending time, plus a value (the text that will be displayed), followed by a blank line (blanklines are separators between elements).
Each element is called “a cue”, and may optionally have an ID that will be useful when using the track element JavaScript API, inparticular the getCueById() method of TextTrack objects.
9 00:00:21.000 --> 00:00:22.000 to hear from you 10 00:00:22.500 --> 00:00:25.000 We want to hear what inspires you as a developer
Opening 00:00:00.000 --> 00:00:30.000 Welcome to our <i>nice film</i>
The displayed text can span multiple lines, but blank lines are not allowed, as they would be interpreted as a separator:
00:01:57.083 --> 00:02:00.000 <p>You're a fool for traveling alone</p> <p>so completely unprepared.</p>
Let’s look at a simple example. First, you need a video on one of the formats/codecs supported by the browsers you target. A recommended codec is mp4/H264, but other formats, such as webm, may have some advantages if the browser supports them. For example, webm allows the video to start playing after a much shorter buffering time. In other words, try if possible to provide the video encoded with more than one codec.
For this, use any sort of open source, free or commercial video encoding software, such as Handbrake (free, open source) or Super (free). There are also online video encoding services, and you can even upload your video to YouTube, let it encode your video in several resolutions and codecs, and use a browser extension such as Video DownloadHelper (for Firefox) or JDownloader, to download the video in your chosen formats.
So, let’s suppose you have a video like the one below (we included it on YouTube for practical reasons). This video has subtitles (you can activate them in the YouTube player), but the goal of this lesson is to explain how we made them without using the YouTube embedded tools, which do not allow export the subtitle file to be exported in the webVTT format.
And if you’ve also got it in mp4/H264 and in webm formats, here is how you can embed it in your page using the video element:
<video id="myVideo" width=500 controls> <source src="videos/SameOldSongAndDanceRogerLathaudPart1MidRes.mp4" type="video/mp4"> <source src="videos/SameOldSongAndDanceRogerLathaudPart1MidRes.webm" type="video/webm"> <track src="videos/subtitles.vtt" kind="subtitles" srclang="en" default> </video>
At line 9, we added a <track> element to add English subtitles, as the guitar teacher there is speaking in French. We will now explain how we created this subtitle track.
Now, we need to create a WebVTT file for this video. How can we synchronize an English translation of what the guitar teacher says in French?
Many tools - both free and commercial - are available to add subtitles to a video. Most are native applications you need to install on your computer. However, a free and very practical tool is available for doing this 100% in a Web browser: amara.
Go to the above Web site, click on the “subtitle a video” link, then follow the different tutorials/instructions. It will ask for a YouTube URL, so it’s better to first upload your video to YouTube (even in private mode). Once you have entered the URL of your video, you will have an online subtitles/caption editor. Enter your subtitles and sync them until you are happy with the results.
Once your subtitles/captions are ok, you will be able to upload them to YouTube, or -this is what we wanted first- download them as WebVTT format:
Note that YouTube can also help you “make subtitles” with its speech recognition tool, but you will only be able to export in .srt format afterwards. You will have to convert this format afterwards to .vtt.
In this section, we will look at different possibilities for styling and positioning the text displayed as captions/subtitles while playing a video.
The example below shows how we can do that (play the video for 40s, look at the positions and styles of the subtitles and captions, look at the HTML):
The WebVTT file is shown below. Notice the new attributes that have been added on the right end of the duration values:
1. WEBVTT 2. 3. 00:00:01.000 --> 00:00:05.000 4. These captions test some features of the WebVTT formats 5. 6. 00:00:06.000 --> 00:00:10.000 line:5% 7. This cue is positioned at the top of the video 8. 9. 00:00:11.000 --> 00:00:15.000 position:5% align:start 10. This cue is positioned at the left side of the video. 11. 12. 00:00:16.000 --> 00:00:20.000 position:95% align:end 13. And this one ate the right side. 14. 15. 00:00:21.000 --> 00:00:25.000 size:33% 16. This cue is only a third of the width of the video, hence the multiple line breaks. 17. 18. 00:00:26.000 --> 00:00:30.000 19. This cue contains <b>bold</b> text. 20. 21. 00:00:31.000 --> 00:00:35.000 22. This cue contains <i>italic</i> text. 23. 24. 00:00:36.000 --> 00:00:40.000 25. This cue contains <u> </u> text. 26. 27. 00:00:41.000 --> 00:00:45.000 28. This cue contains <b><i><u>bold, italic, underlined</u></i></b> text. 29. 30. 00:00:46.000 --> 00:00:50.000 31. <c.myclass>This cue contains the class "myclass". 32. Browsers that support ::cue CSS should make it red.</c> 33. 34. 00:00:51.000 --> 00:00:55.000 35. The following cue contains two voices. 36. Tarzan should be blue and Jane green. 37. 38. 00:00:56.000 --> 00:01:00.000 39. <v Tarzan>Me Tarzan... 40. <v Jane>That would make me Jane! 41. 42. bigtext 43. 00:01:01.000 --> 00:01:05.000 44. This cue has a unique id. 45. Using CSS, its font size should be 150%. 46. 47. 00:01:06.000 --> 00:01:10.000 48. The <00:01:06.333>text <00:01:06.666>in <00:01:07.000>this <00:01:07.333>cue <00:01:07.666>should <00:01:08.000>grow 49. <00:01:08.333>one <00:01:08.666>word <00:01:09.000>at <00:01:09.333>a <00:01:09.666>time 50. 51. 00:01:11.000 --> 00:01:15.000 52. That's it! For now...
The video example tests nearly all the possibilities for positioning subtitles/captions, styling (using HTML element wrapping with <b>, <i>, etc.), voicing (subtitles corresponding to different characters will be displayed in different colors) and CSS styling.
It is possible to locate the cues in the video viewport using absolute or relative values. The attributes that position the text are located on the same line as the cue definition, like at line 9 of the previous WebVTT example file:
9. 00:00:11.000 --> 00:00:15.000<b> position:5% align:start</b> 10. This cue is positioned at the left side of the video.
And so on. Please look at the video as it is self-explanatory.
One can use the HTML elements <b>, <i>, <u> to modify the rendering of subtitles and captions, as illustrated in the example below:
It is possible to style using CSS classes as part of a cue value, using the <c> element. You can specify the CSS class that should be applied by adding “.” followed by the name of your CSS class. Here is an example:
1. <c.myclass>This cue contains the class "myclass". 2. Browsers that support ::cue CSS should make it red.</c>
1. <style type="text/css"> 2. ::cue(.myclass) { color: red; } 3. ::cue(v[voice="Tarzan"]) { color: blue; } 4. ::cue(v[voice="Jane"]) { color: green; } 5. ::cue(#bigtext) { font-size: 150%; } 6. </style>
The ::cue pseudo element selector is used to match “cues” in the webVTT file. You add parenthesis and a secondary CSS selector to match cues that have a particular id, or a particular CSS class, etc. Look at the CSS above and at the extract from the webVTT file, play the video, you will understand how the above CSS classes affect the rendering of the subtitles for Jane and Tarzan’s voices.
Support differs from one browser to another, see this compatibility table (from CanIuse). Note however that most of the enhanced players presented further on in the course provide full support.
Here is an example that shows the voices of the different characters displayed with different colors:
Using the <v> tag, you will distinguish different voices that should be displayed in different colors (depending on the HTML5 video player implementation). See the CSS presented in the previous section to see how to specify the colors for the different voices.
00:00:56.000 --> 00:01:04.000 2. <v Tarzan>Me Tarzan... 3. <v Jane>That would make me Jane!
When you play a movie in DVD or Blu-Ray format, a menu appears on the screen: play a movie, choose subtitles, etc. Usually there is also a “chapters” menu that allows you to quickly access a part of the movie. With videos on the Web, one can also indicate the chapter breakdown, using WebVTT files and a <track> element/tag.
Adding chapters is very similar to adding subtitles/captions. Look at line 5 in the code below, where we use an extra <track> element with a kind=“chapters” attribute.
1. <video poster="webvtt_talk.png" style="width:100%" preload="metadata"> 2. <source src="webvtt_talk.webm"> 3. <source src="webvtt_talk.mp4"> 4. <source src="webvtt_talk.ogv"> 5. <track id="nav" src="webvtt_talk_navigation.vtt" <b>kind="chapters"<b> srclang="en"> 6. <track id="cc" src="webvtt_talk_captions.vtt" kind="captions" 7. label="captions" srclang="en" default> 8. </video>
Here is an example of WebVTT files with defined chapters. Each "CUE" at lines 3, 7, 11, … can bear any name. We use "Chapter 1, Chapter 2, Ending, etc." but you are free to name them as you wish.
What makes them special is that the track has an attribute kind="chapters". Often, the <video> elements rendered in standard browsers ignore chapters, but enhanced HTML5 players take them into account, and it’s not much of a stretch to make your own enhanced player with a nice chapter presentation, as we’ll see in a more advanced chapter of this course. For example, we’ll generate a custom navigation menu, using the <track> JavaScript API (explained later in this section).
1. WEBVTT FILE 2. 3. Chapter 1 4. 00:00:00.000 --> 00:00:10.700 5. Title Slide 6. 7. Chapter 2 8. 00:00:10.700 --> 00:00:47.600 9. Introduction by Naomi Black 10. 11. Chapter 3 12. 00:00:47.600 --> 00:01:50.100 13. Impact of Captions on the Web 14. 15. Chapter 4 16. 00:01:50.100 --> 00:03:33.000 17. Requirements of a Video text format 18. 19. Ending 20. 00:03:33.000 --> 00:04:57.766 21. Simple WebVTT file 22. 23. Greetings 6 24. 00:04:57.766 --> 00:06:16.666 25. Styled WebVTT file
An example of what you can achieve using chapters, using the JW Player
Many tools are available to make and edit HTML5 video and caption/subtitles:
We do not claim that these are the best tools, so feel free to share your discoveries in the discussion forum!
Most “complex” HTML elements like forms, audio or video players, come with a JavaScript API that allows you to control them programmatically, customize them, etc.
This example shows a video with an enhanced progress bar that displays the different chapters as small "clickable" squares. Furthermore, using the JavaScript API of the <track> element, this Web site builds a navigation menu (on the right of the video):
Check this demo (only on Chrome) by Sam Dutton: it shows a video that comes with a WebVTT file that contains longitudes and latitudes. When the video plays, JavaScript functions are called at given times and get the longitude and latitude. A Google Map and a Google Street views are updated in real time.
This example shows how we manage to render music scores in real time as the video plays. Some JavaScript code listens to the ontimeupdate event while the video is playing. We use the currentTime property of the video to know exactly where we are in the video. Finally, we also rely on an external library to render in an HTML5 canvas the bars corresponding to the current video explanations. We render in real time guitar pro tablatures using the alphatab.net library.
There are numerous "enhanced" video players; most are free and open source, some are commercial. They offer lots of features, which are listed below. Not all of these features are available in every player, this list just illustrates what can be added to the standard <video> element.
We call them "HTML5 enhanced video players" because on top of being based on the <video> element, they come with custom features, custom look’n’feel, chapters, etc., based on a JavaScript API that makes such customization possible.
For those of you interested in this particular topic, here is a very good resource that compares most of the players presented in this section, in terms of accessibility. This resource has links to players designed especially for people with disabilities: accessible media players and resources.
Open source, and made for developers, video.js comes with many plugins (chapters, thumbnails etc.).
Either solution (basic player or enhanced player) is good and HTML5 compliant.
Popular players such as JWPlayer have many explanations and examples on their Web sites, and are either free of charge or come with free versions.
Scrub Bar thumbnails (JWPlayer)
Custom look’n’feel and logo (Sublime video player):
Chapters and chapter thumbnails (JWPlayer):
PayPal accessible player:
LeanBack (says "free for non-commercial use", licensing is not very clear…):
HTML5 has introduced, with the getUserMedia and MediaDevices APIs, a way to control webcams and microphones programmatically. This allows, for example, to create an identification form where one can simply click on a button to capture his portrait from the Webcam of his computer or smartphone. It will also allow to record a video or audio extract (for example for a "telephone answering machine" type application), to create video conference applications running in the Web browser, or even musical applications if you plug a guitar into a sound card. The possibilities are numerous.
We will therefore study together how to access the audio and video streams of the hardware on which a Web application is going to run, and how to parameterize them (choice of camera, resolution, etc.).
The getUserMedia API is useful for controlling a Webcam video stream.
This API is one component of the WebRTC specification. When using getUserMedia, to manage a webcam with its video stream, we will always use it in conjunction with the <video> element. We could also use it with the <audio> element if we are only interested in using a microphone (i.e. from a computer), or any sound input .
The getUserMedia API, when dealing with video streams, is always used in conjunction with the <video> element.
The main idea is to set the srcObject attribute of a <video> element to the live video stream object coming out of the Webcam. To get this stream, you’ll have to call the navigator.getUserMedia(params) method from the getUserMedia API, that returns an ES6 promise (ES stands for "ECMAScript" and is the scripting language that forms the basis of JavaScript). Do not panic if you do not know ES6’s promises! The syntax is very simple, and you’ll learn what you need from the provided examples.
The stream is passed as a parameter to the then() method returned by the promise, as in this typical example (you can run it and see the result by clicking on the "CodePen" logo at the top right):
1. <video id="myVideo" autoplay>Fallback msg here.</video> 2. <script> 3. if (navigator.mediaDevices.getUserMedia) { 4. // request video and audio stream from the user's webcam 5. navigator.mediaDevices.getUserMedia({ 6. audio: true, 7. video: true 8. }).then((stream) => { 9. let video = document.querySelector('#myVideo'); 10. video.srcObject = stream; 11. video.play(); 12. }).catch((error) => { 13. console.log('navigator.getUserMedia error: ', error); 14. }); 15. } 16. </script> 17. We can also simplify the code by using a writing with async/await of JavaScript: 18. <video id="myVideo" autoplay>Fallback msg here.</video> 19. <script> 20. async function startWebCam() { 21. // request video and audio stream from the user's webcam 22. let stream = await navigator.mediaDevices.getUserMedia({ 23. audio: true, 24. video: true 25. }); 26. let video = document.querySelector('#myVideo'); 27. video.srcObject = stream; 28. video.play(); 29. } 30. startWebCam(); 31. </script>
HTTPS is mandatory: for getUserMedia to work, it is mandatory to access the page that contains the JavaScript code through https://, for security reasons.
Support of getUserMedia/stream is very good in all modern browsers, including mobile ones. All the video conferencing applications you use that run in a Web browser are based on this API (Google Meet, Jitsi, Bigblue Button), and even Microsoft Teams is a web application in disguise.
Let’s see some more examples of what we can do with the getUserMedia API: start/stop the Webcam, take a screenshot from the current video stream from the Webcam, and apply CSS effects in real time. Below, we give links to some cool examples available on the Web.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Webcam start/stop</title> 6. <script> 7. let webcamStream; 8. function startWebcam() { 9. // request video and audio stream from the user's webcam 10. navigator.mediaDevices.getUserMedia({ 11. audio: true, 12. video: true 13. }).then((stream) => { 14. let video = document.querySelector('#video'); 15. video.srcObject = stream; 16. video.play(); 17. webcamStream = stream; 18. }).catch((error) => { 19. console.log('navigator.getUserMedia error: ', error); 20. }); 21. } 22. function stopWebcam() { 23. webcamStream.getTracks()[0].stop(); // audio 24. webcamStream.getTracks()[1].stop(); // video 25. } 26. </script> 27. </head> 28. <body > 29. <video width=400 height=400 id="video" controls></video> 30. <p> 31. <button onclick="startWebcam();">Start WebCam</button> 32. <button onclick="stopWebcam();">Stop WebCam</button> 33. </p> 34. </body> 35. </html>
In order to stop the Webcam and make the hardware "unlock it", you need to call the stop() method of the video stream.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Webcam start/stop</title> 6. <script> 7. let webcamStream; 8. 9. function startWebcam() { 10. // request video and audio stream from the user's webcam 11. navigator.mediaDevices.getUserMedia({ 12. audio: true, 13. video: true 14. }).then((stream) => { 15. let video = document.querySelector('#video'); 16. video.srcObject = stream; 17. video.play(); 18. 19. webcamStream = stream; 20. }).catch((error) => { 21. console.log('navigator.getUserMedia error: ', error); 22. }); 23. } 24. 25. function stopWebcam() { 26. webcamStream.getTracks()[0].stop(); // audio 27. webcamStream.getTracks()[1].stop(); // video 28. } 29. </script> 30. </head> 31. <body> 32. <video width=400 height=400 id="video" controls></video> 33. <p> 34. <button onclick="startWebcam();">Start WebCam</button> 35. <button onclick="stopWebcam();">Stop WebCam</button> 36. </p> 37. </body> 38. </html>
Try this example that shows how to use the getUserMedia API. Note the CSS effects (click on the video to cycle from one effect to another):
//~~~~~~~~~~~~~~~~~~~~ // GET USER MEDIA CODE //~~~~~~~~~~~~~~~~~~~~ let video; let webcamStream; function startWebcam() { // request video and audio stream from the user's webcam navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then((stream) => { let video = document.querySelector('#video'); video.srcObject = stream; video.play(); webcamStream = stream; }).catch((error) => { console.log('navigator.getUserMedia error: ', error); }); } function stopWebcam() { webcamStream.getTracks()[0].stop(); // audio webcamStream.getTracks()[1].stop(); // video } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CODE FOR CHANGING CSS FILTERS //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ let idx = 0; let filters = [ 'grayscale', 'sepia', 'blur', 'brightness', 'contrast', 'hue-rotate', 'hue-rotate2', 'hue-rotate3', 'saturate', 'invert' ]; function changeFilter(el) { // Remove all CSS classes for element el el.className = ''; // Choose a CSS class name console.log("toggling effect: " + filters[idx filters.length]); let effect = filters[idx++ % filters.length]; el.classList.add(effect); }
#output { width: 307px; height: 250px; background: rgba(255,255,255,0.5); border: 1px solid #ccc; } #screenshot-stream { width: initial; height: initial; } #screenshot { vertical-align: top; } .blur { filter: blur(3px); } .brightness { filter: brightness(5); } .contrast { filter: contrast(8); } .hue-rotate { filter: hue-rotate(90deg); } .hue-rotate2 { filter: hue-rotate(180deg); } .hue-rotate3 { filter: hue-rotate(270deg); } .saturate { filter: saturate(10); } .grayscale { filter: grayscale(1); } .sepia { filter: sepia(1); } .invert { filter: invert(1) }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Webcam and CSS effects</title> </head> <body> <h1>Example of CSS effects on a live video stream</h1> <p> Click the button "start webcam at the end of this page, then click on the video to toggle the different effects.</p> </p> <video onclick="changeFilter(this);" width=400 height=400 id="video" controls autoplay></video> <p> <button onclick="startWebcam();">Start WebCam</button> <button onclick="stopWebcam();">Stop WebCam</button> </p> </body> </html>
The trick is to copy and paste the current image from the video stream into a <canvas> element.
//~~~~~~~~~~~~~~~~~~~~ // GET USER MEDIA CODE //~~~~~~~~~~~~~~~~~~~~ let video; let webcamStream; function startWebcam() { // request video and audio stream from the user's webcam navigator.mediaDevices.getUserMedia({ audio: true, video: true > }).then((stream) => { video = document.querySelector('#video'); video.srcObject = stream; video.play(); webcamStream = stream; }).catch((error) => { console.log('navigator.getUserMedia error: ', error); }); } function stopWebcam() { webcamStream.getTracks()[0].stop(); // audio webcamStream.getTracks()[1].stop(); // video } //~~~~~~~~~~~~~~~~~~~~~ // TAKE A SNAPSHOT CODE //~~~~~~~~~~~~~~~~~~~~~ var canvas, ctx; function init() { // Get the canvas and obtain a context for // drawing in it canvas = document.getElementById("myCanvas"); ctx = canvas.getContext('2d'); } function snapshot() { // Draws current image from the video element into the canvas ctx.drawImage(video, 0,0, canvas.width, canvas.height); }
canvas { border:1px solid black; }
<!DOCTYPE html> <html lang="en"> <head> <title>WebCam screenshot</title> <meta charset="utf-8"/> </head> <body onload="init();"> <h1>Take a snapshot of the current video stream</h1> Check the CSS and JavaScript tabs. Click on the Start WebCam button. <p> <button onclick="startWebcam();">Start WebCam</button> <button onclick="stopWebcam();">Stop WebCam</button> <button onclick="snapshot();">Prendre un screenshot</button> </p> <video onclick="snapshot(this);" width=200 height=200 id="video" controls autoplay></video> <p> Screenshots : </p> <canvas id="myCanvas" width="200" height="150"></canvas> </body> </html>
We will look at this example in greater detail in the next course section (related to the <canvas> element).
Instead of using the getUserMedia API with: navigator.getUserMedia({video:true}, onSuccess, onError), it is also possible to use {audio:true} for the first parameter. In this case, only the microphone input will be captured. Notice that {video:true, audio:true} is also accepted, if you write a video conferencing system and need to capture both the audio and the video (this is often the case when writing WebRTC applications).
Apart from videoconferencing, microphone input will be used for music Web apps, from the WebAudio API. This API focuses on real time sound processing and music synthesis. This API is covered in the advanced W3Cx HTML5 course (HTML5 Apps and Games).
Check out the WebAudio demonstrations written by Chris Wilson, esp. the one called “Input effects”.
The image below is taken from one of the demonstrations, where the sound captured by the microphone is processed in real time. We get visualizations of the signal in real time as an animated waveform, animated frequencies or animated audiogram. If we connect an electric guitar to the sound card input, then this demonstration shows that we can recreate with WebAudio most of the classic effects used by guitarists (delay, reverb, distortion, chorus, etc.).
It is possible to set “hints” for the preferred cam/resolution during video capture. This is done by using a "constraint" object that is passed as a parameter to the getUserMedia(…) method. It’s just the same object we passed in the basic example: navigator.getUserMedia({video:true}, success, error) except that this time this object is a little more complex by including new properties in addition to video:true or audio:true.
For more information, this article on MDN about the getUserMedia API gives great examples on how to set the camera resolution and/or to choose the front or back camera when using a mobile phone.
var vgaButton, qvgaButton, hdButton, dimensions, video, stream; function init() { vgaButton = document.querySelector('button#vga'); qvgaButton = document.querySelector('button#qvga'); hdButton = document.querySelector('button#hd'); dimensions = document.querySelector('p#dimensions'); video = document.querySelector('video'); // Defines event listeners for the buttons that set the resolution qvgaButton.onclick = function() { getMedia(qvgaConstraints); }; vgaButton.onclick = function() { getMedia(vgaConstraints); }; hdButton.onclick = function() { getMedia(hdConstraints); }; // Trick: check regularly the size of the video element and display it // When getUserMedia is called the video element changes it sizes but for // a while its size is zero pixels... o we check every half a second video.addEventListener('play', function() { setTimeout(function() { displayVideoDimensions(); }, 500); }); } // The different values for the constraints on resolution var qvgaConstraints = { video: { width: { max: 320 }, height: { max: 180 } } }; var vgaConstraints = { video: { width: { max: 640 }, height: { max: 360 } } }; var hdConstraints = { video: { width: { min: 1280 }, height: { min: 720 } } }; // The function that is called when a button has been clicked: starts the video // with the preferred resolution function getMedia(constraints) { if (!!stream) { video.srcObject = null; stream.getTracks()[0].stop(); } navigator.mediaDevices.getUserMedia(constraints) .then((stream) => { video.srcObject = stream; video.play(); window.stream = stream; }) .catch((error) =>{ console.log('navigator.getUserMedia error: ', error); }); } // util function that is called by the setInterval(...) every 0.5s, for // displaying the video dimensions function displayVideoDimensions() { dimensions.innerHTML = 'Actual video dimensions: ' + video.videoWidth + 'x' + video.videoHeight + 'px.'; }
video { border:1px solid; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>getUserMedia constraints for Webcam resolutions</title> </head> <body onload="init();"> <h1>Set the camera resolution</h1> Example adapted from: <a href="https://www.simpl.info/getusermedia/constraints/"> https://www.simpl.info/getusermedia/constraints/ </a> <br> <p>Click a button to call <code>getUserMedia()</code> with appropriate resolution. </p> <div id="buttons"> <button id="qvga">QVGA</button> <button id="vga">VGA</button> <button id="hd">HD</button> </div> <p id="dimensions"></p> <video autoplay></video> </body> </html>
1. var vgaConstraints = { 2. video: { 3. width: { max: 640 }, 4. height: { max: 360 } 5. } 6. }; 7. var hdConstraints = { 8. video: { 9. width: { min: 1280 }, 10. height: { min: 720 } 11. } 12. }; 13. 14. let constraints = hdConstraints; 15. navigator.mediaDevices.getUserMedia(constraints) 16. .then((stream) => {...}
Use this Web app that systematically tests a set of "preferred resolutions" and compared them to the actual resolutions returned by the browser. Remember that the requested resolution is a hint, and there is no real guarantee that your configuration will allow it.
Here are some other constraints you can set. In particular, look at the ones for selecting the front or rear camera (smartphones):
1. // more on video resolution 2. constraints = { 3. video: { 4. width: { min: 1024, ideal: 1280, max: 1920 }, 5. height: { min: 776, ideal: 720, max: 1080 } 6. } 7. } 8. // Framerate 9. constraints = { video: { frameRate: { ideal: 10, max: 15 } } }; 10. 11. // front and back camera (mobile), some examples 12. var front = false; 13. 14. document.getElementById('flip-button').onclick = function() { front = !front; }; 15. // toggle front and back camera (mobile) by clicking a button 16. constraints = { video: { facingMode: (front? "user" : "environment") } }; 17. 18. // prefer front camera 19. constraints = { audio: true, video: { facingMode: "user" } } 20. 21. // require rear camera 22. constraints = { audio: true, video: { facingMode: { exact: "environment" } } }
1. function gotDevices(deviceInfos) { 2. for (var i = 0; i !== deviceInfos.length; ++i) { 3. var deviceInfo = deviceInfos[i]; 4. console.log("device with id: " + deviceInfo.deviceId); 5. // possible values: audioinput, audiooutput, videoinput 6. console.log("device with kind: " + deviceInfo.kind); 7. // 'speaker' or 'camera' for example 8. console.log("device with label: " + deviceInfo.label); 9. //... should build a menu, test kind/label and set 10. // audioSource and videoSource variables 11. } 12. } 13. // ... 14. var constraints = { 15. audio: {deviceId: audioSource ? {exact: audioSource} : undefined}, 16. video: {deviceId: videoSource ? {exact: videoSource} : undefined} 17. }; 18. 19. navigator.mediaDevices.getUserMedia(constraints). 20. then(gotStream).then(gotDevices).catch(handleError)
This MediaRecoredr API allows to record / capture the audio or video stream. There are many sources for audio or video streams, but we will only consider here the streams coming from a WebCam or a sound input (i.e. microphone).
For example, the MediaRecorder API is used to record the video stream from a WebCam as a file saved on the hard disk. Below is a screenshot of an application allowing to record the WebCam. You will be able to run this example a little further, but for security reasons, it cannot run directly in this Web page.
Let’s record, replay and download the video stream captured using a Webcam. You can test it below by clicking on "CodePen" at the top right:
var mediaRecorder; var recordedBlobs; var gumVideo = document.querySelector('video#gum'); var recordedVideo = document.querySelector('video#recorded'); var recordButton = document.querySelector('button#record'); var playButton = document.querySelector('button#play'); var downloadButton = document.querySelector('button#download'); recordButton.onclick = toggleRecording; playButton.onclick = play; downloadButton.onclick = download; // get stream using getUserMedia navigator.mediaDevices.getUserMedia({ audio: true,video: true}) .then((stream) => { recordButton.disabled = false; console.log('getUserMedia() got stream: ', stream); window.stream = stream; gumVideo.srcObject = stream; }) .catch((error) => { console.log('navigator.getUserMedia error: ', error); }); function handleDataAvailable(event) { if (event.data && event.data.size > 0) { recordedBlobs.push(event.data); } } function handleStop(event) { console.log('Recorder stopped: ', event); } function toggleRecording() { if (recordButton.textContent === 'Start Recording') { startRecording(); } else { stopRecording(); recordButton.textContent = 'Start Recording'; playButton.disabled = false; downloadButton.disabled = false; } } // create the media recorder function startRecording() { recordedBlobs = []; try { mediaRecorder = new MediaRecorder(window.stream); } catch (e) { console.error('Exception while creating MediaRecorder: ' + e); return; } console.log('Created MediaRecorder', mediaRecorder); recordButton.textContent = 'Stop Recording'; playButton.disabled = true; downloadButton.disabled = true; mediaRecorder.onstop = handleStop; mediaRecorder.ondataavailable = handleDataAvailable; mediaRecorder.start(10); // collect 10ms of data console.log('MediaRecorder started', mediaRecorder); } function stopRecording() { mediaRecorder.stop(); console.log('Recorded Blobs: ', recordedBlobs); recordedVideo.controls = true; } function play() { var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'}); recordedVideo.src = window.URL.createObjectURL(superBuffer); } function download() { var blob = new Blob(recordedBlobs, {type: 'video/webm'}); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = 'test.webm'; document.body.appendChild(a); a.click(); setTimeout(function() { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 100); }
/* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ button { margin: 0 3px 10px 0; padding-left: 2px; padding-right: 2px; width: 99px; } button:last-of-type { margin: 0; } p.borderBelow { margin: 0 0 20px 0; padding: 0 0 20px 0; } video { height: 232px; margin: 0 12px 20px 0; vertical-align: top; width: calc(20em - 10px); } video:last-of-type { margin: 0 0 20px 0; } video#gumVideo { > margin: 0 20px 20px 0; } @media screen and (max-width: 500px) { button { font-size: 0.8em; width: calc(33% - 5px); } } @media screen and (max-width: 720px) { video { height: calc((50vw - 48px) * 3 / 4); margin: 0 10px 10px 0; width: calc(50vw - 48px); } video#gumVideo { margin: 0 10px 10px 0; } }
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>MediaRecorder API usage</title> </head> <body> <div id="container"> <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC example:</a> <span>MediaRecorder</span></h1> <p>For more info, see the <a href="https://w3c.github.io/mediacapture-record/MediaRecorder.html" title="W3C MediaStream Recording API - Editor's Draft">MediaStream Recording API Editor's Draft</a>.</p> <video id="gum" autoplay muted></video> <video id="recorded" loop controls></video> <div> <button id="record" disabled>Start Recording</button> <button id="play" disabled>Play</button> <button id="download" disabled>Download</button> </div> <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/record" title="View source for this page on GitHub" id="viewSource">View source on GitHub</a> </div> </body> </html>
Click “start recording”, then press the play button on the video element on the right of the app. You can also click the “download” button to download a .webm file, playable offline with a media player such as VLC or online in a Web page with the <video> element.
var options = {mimeType: 'video/webm; codecs=vp9'}; mediaRecorder = new MediaRecorder(stream, options);
… where stream is typically the object returned by the call to getUserMedia (see previous examples).
1. var recordedChunks = []; // will hold the recorded stream 2. mediaRecorder.ondataavailable = handleDataAvailable; 3. mediaRecorder.start(); 4 5. function handleDataAvailable(event) { 6. if (event.data.size > 0) { 7. recordedChunks.push(event.data); 8. } else { 9. // ... 10. }
When you’re done, you need to call the stop() method of the mediaRecorder object. This will end the periodic execution of the handleDataAvailable method, and stop the data capture.
mediaRecorder.stop();
This piece of code creates a blob with the recordedChunks array. Use the URL.createObjectURL(recordedChunks) standard method to create another object that can be used as a value to set the src attribute of an HTML5 video element.
Like that, the recorded stream can be played using a standard HTML5 <video> element.
function play() { var superBuffer = new Blob(recordedChunks); videoElement.src = window.URL.createObjectURL(superBuffer); }
A trick consists in creating, on the fly, an invisible link with a download attribute (see Module 1) and a href attribute that points to the blob object containing the recorded stream encoded using a given codec, then generate programmatically a click event on the link. This will force the browser to download a file of type video/webm to the hard disk.
1. function download() { 2. var blob = new Blob(recordedChunks, { 3. type: 'video/webm' 4. }); 5. var url = URL.createObjectURL(blob); 6. var a = document.createElement('a'); 7. document.body.appendChild(a); 8. a.style = 'display: none'; 9. a.href = url; 10. a.download = 'test.webm'; 11. a.click(); 12. window.URL.revokeObjectURL(url); 13. }
HTML5 is composed of new elements, but it also comes with many JavaScript APIs for controlling video and sound, drawing and animating things in the new <canvas> element, for offline applications, persistence, geolocation, orientation, etc.
So yes, during this course, in particular in Modules 3 and 4, you will have to do a bit of JavaScript. But, DON’T PANIC!
Here we provide a basic introduction to JavaScript. If you want to learn more, many resources are available on the Web; this document is simply here to give you a head start. Remember that one great thing about these MOOCs courses is that everybody can help each other. Some students are very good in JavaScript and are usually very happy to help others when they encounter difficulties.
You will learn a lot by looking at examples, tweaking them, cloning and modifying them, etc. Many previous students who were real JavaScript beginners managed to do [all] the assignments (drawing and animating a monster with keyboard/mouse interaction)! And they did this by just studying the provided examples.
We will not look at the JavaScript syntax here, but more at "JavaScript in the browser", how it works, how to start writing code, etc.
First of all, you need to find a way to debug your code and see errors. If your work does not produce any results, you must know why! For that you will use the dev. tools of your browser. Press F12 in Windows or cmd-alt-i in Mac to open the dev. tools, then go to the console tab: this is where errors will be displayed, or messages of your own (use the console.log(string) JavaScript function in the JavaScript code embedded in your html page). In the console, you will be able to type any JavaScript command.
Let’s look at this example on JS Bin:
<div> <table> <colgroup> <col style="width: 1%" /> <col style="width: 98%" /> </colgroup> <thead> <tr class="header"> <th>#</th> <th>HTML code</th> </tr> </thead> <tbody> <tr class="odd"> <td>1</td> <td><!DOCTYPE html></td> </tr> <tr class="even"> <td>2</td> <td><<a href="https://december.com/html/4/element/html.html" target="_blank" rel="noopener noreferrer"><b>html lang=“en”</b></a>></td> </tr> <tr class="odd"> <td>3</td> <td><<a href="https://december.com/html/4/element/head.html" target="_blank" rel="noopener noreferrer"><b>head</b></a>></td> </tr> <tr class="even"> <td>4</td> <td><<a href="https://december.com/html/4/element/meta.html" target="_blank" rel="noopener noreferrer"><b>meta</b></a> charset=utf-8 /></td> </tr> <tr class="odd"> <td>5</td> <td><<a href="https://december.com/html/4/element/title.html" target="_blank" rel="noopener noreferrer"><b>title</b></a>> Web Audio API</<a href="https://december.com/html/4/element/title.html" target="_blank" rel="noopener noreferrer"><b>title</b></a>></td> </tr> <tr class="even"> <td>6</td> <td><<a href="https://december.com/html/4/element/script.html" target="_blank" rel="noopener noreferrer"><b>script</b></a>></td> </tr> <tr class="odd"> <td>7</td> <td>console.log("Some JavaScript code has been executed");</td> </tr> <tr class="even"> <td>8</td> <td></<a href="https://december.com/html/4/element/script.html" target="_blank" rel="noopener noreferrer"><b>script</b></a>></td> </tr> <tr class="odd"> <td>9</td> <td></<a href="https://december.com/html/4/element/head.html" target="_blank" rel="noopener noreferrer"><b>head</b></a>></td> </tr> <tr class="even"> <td>10</td> <td><<a href="https://december.com/html/4/element/body.html" target="_blank" rel="noopener noreferrer"><b>body</b></a>></td> </tr> <tr class="odd"> <td>11</td> <td><<a href="https://december.com/html/4/element/h1.html" target="_blank" rel="noopener noreferrer"><b>h1</b></a>>JavaScript debugging using the dev tool console</<a href="https://december.com/html/4/element/h1.html" target="_blank" rel="noopener noreferrer"><b>h1</b></a>></td> </tr> <tr class="even"> <td>12</td> <td></<a href="https://december.com/html/4/element/body.html" target="_blank" rel="noopener noreferrer"><b>body</b></a>></td> </tr> <tr class="odd"> <td>13</td> <td></<a href="https://december.com/html/4/element/html.html" target="_blank" rel="noopener noreferrer"><b>html</b></a>></td> </tr> </tbody> </table> </div>
The simplest way to add JavaScript code in an HTML page, is by using the <script>…</script> element.
The code in this example is executed sequentially when the page is loaded: the JavaScript code is executed before the browser could see the rest of the page (as the <script></script> is located before the <body>).
The H1 element, for example, does not exist in the Document Object Model, and has not yet been displayed when the JavaScript code is executed. If we move the <script></script> at the end of the document, then the H1 would have been built before the JavaScript code is executed.
The only line of code we have is console.log(“Some JavaScript code has been executed”);
This means “display in the JavaScript console the message…”. If we open the console tab provided by jsbin.com in a dedicated tab (that redirects all console.log() messages), and re-execute the page (just type a space at the end of a line, this will re-render the page and display the message in the console), we see the message in the console tab, as well as in the dev. tools console. This is illustrated by the image below:
It is also possible to use the “real dev. tool console”, and for this I recommend running the application in a single window, not in the JS Bin editor. Press the black arrow on the top right of the output window - this will render the page as a standalone Web page, then press F12. You should see:
Ok, now, let’s make an error: change console.log() into consollle.log(). Let’s see what happens:
And if we run it standalone and use the dev. tool console:
And if we click on the line number in the right, the dev. tool shows the source code centered on the line that caused the error:
Without such tools, debugging JavaScript code is impossible. So you need to look at some basic tutorials on how to use the dev. tools of your browsers, since they differ from one another in the way they work - although the principles remain the same.
Some of you may not be used to “asynchronous programming”, “callbacks” etc. We recommend to read this article on WikiPedia and this thread on StackOverflow.
The <canvas> tag was introduced into the HTML specification around 2010 as a “Flash killer.” At the time, popular video games used this proprietary technology and only a few browsers supported it. The HTML canvas allows drawing and animation at 60 frames per second, in 2D or 3D.
The canvas has been designed for pixel-based graphics, while SVG (Scalable Vector Graphics, another W3C standard) is for vector-based graphics.
Indeed, the canvas JavaScript drawing API supports different kind of shapes: lines, rectangles, ellipses, arcs, curves, text, images. Some drawing styles need to be specified that will affect the way shapes are drawn (color, drawing width, shadows, etc.). An alpha channel for drawing in transparent mode is also supported, as well as many advanced drawing modes and global filters (blur, etc.).
The canvas is also used to do animations at 60 frames per second (useful for games), to display videos with special effects, to display a webcam stream, and so on.
Here are some fun examples that show the interest of the HTML5 canvas.
Foot Chinko is one popular free HTML5 games:
Lots of data visualization tools and JavaScript libraries use the HTML5 canvas element for Data visualization:
A version of the arcade game Galaxian, that runs at 60 frames per second in an HTML5 canvas element:
Performance is good and animation is generally very smooth, since most Web browsers (mobile and desktop) support hardware acceleration.
Note: 3D drawing using the WebGL API is also possible in a <canvas>, but will not be covered in this course. For the most curious among you, please have a look at the two popular libraries for doing 3D drawing/animation in a <canvas>: BabylonJS and ThreeJS.
The dynamic nature of the <canvas> element has made it difficult to use in applications that need to be accessible to people with disabilities. To be accessible, it must meet the following principles:
We recommend these 2 quick references (or cheatsheets) below. Do not hesitate to keep your favorite one open in a separate browser ta
The coordinate system used for drawing in canvases is similar to the one used by many drawing APIs like Java2D: the (0 , 0) is in the top left corner while the X axis is going to the right and the Y axis to the bottom, as shown in the following picture:
Small errata about what I said in the above video: “So let’s get the canvas using the DOM API method document.getElementById() or better, use document.querySelector() that is a more recent method from the DOM API”..
The part is bold is not correct: querySelector, technically, comes from Selectors API. Just in case some people would like to check the specification.
Here are the different steps, in a little more detail, of the example demonstrated in the above video:
<canvas id="myCanvas" width="300" height="225"> Fallback content that will be displayed in case the web browser does not support the canvas tag or in case scripting is disabled. </canvas>
Place code similar to the above somewhere in an HTML page. This example defines an area of 300 by 225 pixels on which content can be rendered with JavaScript.
Normally you should see nothing as a result; by default canvases are “transparent”. Make it visible using CSS! For example, you can add a border to the canvas (or change the background color, or put an image in the background).
<style> #myCanvas { border:1px solid black; } </style>
We can have more than one <canvas> in a single page, and canvases will be manipulated with JavaScript like other elements in the DOM.
var canvas = document.getElementById("myCanvas");
var canvas = document.querySelector("#myCanvas");
This step is useful for drawing and setting drawing properties (color, etc.)
Once we have a pointer to the <canvas>, we can get a “context”. This particular object is the core of the canvas JavaScript API. It provides methods for drawing, like fillRect(x, y, width, height) for example, that draws a filled rectangle, and properties for setting the color, shadows, gradients, etc.
var ctx=canvas.getContext('2d');
ctx.fillStyle='red';
ctx.fillRect(0,0,80,100);
<!DOCTYPE html> <html lang="en"> <head> <style> #myCanvas { border: 1px solid black; } </style> <title>Canvas</title> <meta charset="utf-8"/> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw drawSomething(); } function drawSomething() { // draw a red rectangle ctx.fillStyle='#FF0000'; ctx.fillRect(0,0,80,100); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="200" height="200"> Your browser does not support the canvas tag. </canvas> </body> </html>
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <style> 5. #myCanvas { 6. border: 1px solid black; 7. } 8. </style> 9. <title>Canvas</title> 10. <meta charset="utf-8"/> 11. <script> 12. var canvas, ctx; 13. function init() { 14. // This function is called after the page is loaded 15. // 1 - Get the canvas 16. canvas = document.getElementById('myCanvas'); 17. // 2 - Get the context 18. ctx=canvas.getContext('2d'); 19. // 3 - we can draw 20. drawSomething(); 21. } 22. function drawSomething() { 23. // draw a red rectangle 24. ctx.fillStyle='#FF0000'; 25. ctx.fillRect(0,0,80,100); 26. } 27. </script> 28. </head> 29. <body onload="init();"> 30. <canvas id="myCanvas" width="200" height="200"> 31. Your browser does not support the canvas tag. 32. </canvas> 33. </body> 34. </html>
Only access elements when the DOM is ready:
Notice that we wrote an “init” function (line 13) that is called only when the page has been entirely loaded (we say “when the DOM is ready”). There are several ways to do this. In this example we used the <body onload=“init();”> method, at line 29.
It’s good practice to have such a function, as we cannot access the elements of the page before the page has been loaded entirely and before the DOM is ready.
Another way is to put the JavaScript code at the end of the document (between <script>…</script>), right before the </body>. In this case when the JavaScript code is executed, the DOM has already been constructed.
Start by getting the canvas and the context:
Before drawing or doing anything interesting with the canvas, we must first get its drawing “context”. The drawing context defines the drawing methods and properties we can use.
Good practice is to get the canvas, the context, the width and height of the canvas and other global objects in this “init” function.
After the context is set, we can draw, but first let’s set the current color for filled shapes:
The example shows the use of the fillStyle property at line 24 - useful for specifying the way shapes will be filled. In our case this line indicates the color of all the filled shapes we are going to draw:
ctx.fillStyle='#FF0000';
The context property named fillStyle is used here. This property can be set with a color, a gradient, or a pattern. We will see examples of these later on in the course.
The example says that all filled shapes will use the color “#FF0000”, which corresponds to a pure red color using the CSS RGB hexadecimal encoding (we could also have used ctx.fillStyle=‘red’);
Then we can draw:
ctx.fillRect(0,0,80,100);
This line is a call to the method fillRect(top left X coordinate, top left Y coordinate, width, height), which draws a filled rectangle.
The way the rectangle will be filled depends on the current value of several properties of the context, in particular the value of the fillStyle property. In our case, the rectangle will be red.
<canvas id="myCanvas" width="200" height="200"> ...fallback content... </canvas>
var canvas=document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
ctx.fillStyle='#FF0000';
ctx.fillRect(0,0,80,100)
Before we go on, we should take some time to clarify the way we draw on HTML5 canvases. We already mentioned that we use a graphic context for all the main operations. Whenever a shape, a text, or an image is drawn, the current values of the different properties of the graphic context are taken into account. Some are relevant only for certain kinds of shapes or drawing modes, but you must be aware that it is always the current values of these drawing properties that are used.
Later on we’ll see that there are ways to save and restore this whole set of values, but for now, let’s examine in greater detail some of the properties and methods we’ve already encountered, and introduce new ones.
The default value is the color black. Any kind of drawing in “fill mode” will use the value of this property to determine how to render the “filled part” of the drawing: any filled rectangle will be filled black by default, any filled circle will be filled in black, and so on.
As long as we don’t modify the value of this property, all drawing commands for filled shapes will use the current value.
Note that we will study in detail how to use colors, gradients and patterns later, but for now we introduce some properties and values so that you can understand the principles of canvas drawing.
fillStyle and the other context properties can be considered to be “global variables” of the context.
The two first parameters are the coordinates of the top left corner of the rectangle. This method uses the current value of the fillStyle property to determine how to fill the rectangle.
ctx.fillStyle='pink'; ctx.fillRect(10,10,200,200);
Produces this result:
The possible values are the same as those for the fillStyle property: a color, a pattern, or a gradient. This property will be taken into account when wireframe shapes are drawn.
ctx.strokeStyle='blue'; ctx.strokeRect(10,10,200,200);
… gives this result:
Only the outline of the rectangle will be drawn, and it will be drawn using the value of the strokeStyle property.
Actually it draws it in a color called “transparent black” (!) that corresponds to the initial state of the rectangle as if no drawing had occurred.
ctx.fillStyle='pink'; ctx.fillRect(10,10,200,200); ctx.clearRect(50, 50, 20, 20);
1. function drawSomething() { 2. // draw a red rectangle, line width=3 pixels 3. ctx.lineWidth=3; 4. ctx.strokeStyle='red'; 5. ctx.strokeRect(10,10,80,100); 6. }
Here, we used "stroke" instead of "fill" in the property and method names (lines 4 and 5): strokeStyle instead of fillStyle, strokeRect(…) instead of fillRect(…).
We also introduced a new property of the context, that applies only when drawing in "stroke" mode, the lineWidth property (line 3), that is used for setting the width of the shape outline. The value is in pixels.
Let’s continue with another example. This time we will draw several shapes that share the same colors - they will be filled in red, with a blue outline. We also show how to draw a text message with a given font.
<!DOCTYPE html> <html lang="en"> <head> <title>Drawing with outline</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw drawSomething(); } function drawSomething() { // set the global context values ctx.lineWidth=5; ctx.fillStyle='red'; ctx.strokeStyle='blue' // font for all text drawing ctx.font = 'italic 20pt Calibri'; // Draw the two filled red rectangles ctx.fillRect(10, 30, 70, 150); ctx.fillRect(110, 30, 70, 150); // Draw the two blue wireframe rectangles ctx.strokeRect(10, 30, 70, 150); ctx.strokeRect(110, 30, 70, 150); // Draw a message above the rectangles ctx.fillText("hello", 70, 22); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="200" height="200"> Your browser does not support the canvas tag. </canvas> </body> </html>
1. function drawSomething() { 2. // set the global context values 3. ctx.lineWidth=5; 4. ctx.fillStyle='red'; 5. ctx.strokeStyle='blue' 6. // font for all text drawing 7. ctx.font = 'italic 20pt Calibri'; 8. 9. // Draw the two filled red rectangles 10. ctx.fillRect(10, 30, 70, 150); 11. ctx.fillRect(110, 30, 70, 150); 12. 13. // Draw the two blue wireframe rectangles 14. ctx.strokeRect(10, 30, 70, 150); 15. ctx.strokeRect(110, 30, 70, 150); 16. 17. // Draw a message above the rectangles 18. ctx.fillText("hello", 70, 22); 19. }
This example shows the “global” nature of the context properties. Once you set the filled color to red, any shapes you draw in filled mode will be red. This is true for all the context properties. We set some of these properties in lines 3-7, and all following calls to context methods for drawing rectangles or text will depend on them. The two filled rectangles at lines 10-11 will be red, the two wireframe rectangles drawn at lines 14-15 will be blue, etc.
Line 18 shows how to draw a text message at an X position of 70 and a Y position of 22. The font is set at line 7 using the font property of the context. The syntax is the same we use in CSS for using “system fonts”.
If you would like to draw the filled text message in green, for example, you should set the ctx.fillStyle property to “green” after you draw the rectangles and before you draw the text (i.e just before line 18).
We now introduce the basics of 2D transformations, a powerful tool that will make things easier as soon as you have to:
Let’s start with some simple examples before looking at how we use 2D transforms.
If we draw three rectangles of size 100x200 in a 400x400 canvas, one at (0, 0) and another at (150, 0), and a third at (300, 0), here is the result and the corresponding code:
<!DOCTYPE html> <html lang="en"> <head> <title>Let's draw three rectangles!</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw drawSomething(); } function drawSomething() { // draw a red rectangle ctx.fillStyle='lightgreen'; ctx.fillRect(0,0,100,200); ctx.fillRect(150,0,100,200); ctx.fillRect(300,0,100,200); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
JavaScript code extract:
1. function drawSomething() { 2. ctx.fillStyle='lightgreen'; 3. 4. ctx.fillRect(0,0,100,200); 5. ctx.fillRect(150,0,100,200); 6. ctx.fillRect(300,0,100,200); 7. }
What if we wanted to draw these 3 rectangles at another position, as a group? We would like to draw all of them a little closer to the bottom, for example… Let’s add some parameters to the function: the X and Y position of the rectangles.
<!DOCTYPE html> <html> <head lang="en"> <title>Draw 3 rectangles at any X and Y position</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw drawSomething(0, 100); } function drawSomething(x, y) { // draw a red rectangle ctx.fillStyle='lightgreen'; ctx.fillRect(x,y,100,200); ctx.fillRect(x+150,y,100,200); ctx.fillRect(x+300,y,100,200); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
1. var canvas, ctx; 2. 3. function init() { 4. // This function is called after the page is loaded 5. // 1 - Get the canvas 6. canvas = document.getElementById('myCanvas'); 7. // 2 - Get the context 8. ctx=canvas.getContext('2d'); 9. // 3 - we can draw 10. drawSomething(0, 100); 11. } 12. 13. function drawSomething(x, y) { 14. // draw 3 rectangles 15. ctx.fillStyle='lightgreen'; 16. ctx.fillRect(x,y,100,200); 17. ctx.fillRect(x+150,y,100,200); 18. ctx.fillRect(x+300,y,100,200); 19. }
At line 10, we called the drawSomething(…) function with 0 and 100 as parameters, meaning “please add an offset of 0 in X and 100 in Y directions to what is drawn by the function…
If you look at the code of the modified function, you will see that each call to fillRect(…) uses the x and y parameters instead of hard coded values. In this way, if we call it with parameters (0, 100), then all rectangles will be drawn 100 pixels to the bottom (offset in y). Here is the result:
Now we can start having some fun… let’s draw a monster’s head using only rectangles:
<!DOCTYPE html> <html lang="en"> <head> <title>Monster's head with rectangles</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw drawMonster(0, 0); } function drawMonster(x, y) { // draw a red rectangle // head ctx.fillStyle='lightgreen'; ctx.fillRect(x,y,200,200); // eyes ctx.fillStyle='red'; ctx.fillRect(x+35,y+30,20,20); ctx.fillRect(x+140,y+30,20,20); // interior of eye ctx.fillStyle='yellow'; ctx.fillRect(x+43,y+37,10,10); ctx.fillRect(x+143,y+37,10,10); // Nose ctx.fillStyle='black'; ctx.fillRect(x+90,y+70,20,80); // Mouth ctx.fillStyle='purple'; ctx.fillRect(x+60,y+165,80,20); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
1. function drawMonster(x, y) { 2. // head 3. ctx.fillStyle='lightgreen'; 4. ctx.fillRect(x,y,200,200); 5. 6. // eyes 7. ctx.fillStyle='red'; 8. ctx.fillRect(x+35,y+30,20,20); 9. ctx.fillRect(x+140,y+30,20,20); 10. 11. // interior of eye 12. ctx.fillStyle='yellow'; 13. ctx.fillRect(x+43,y+37,10,10); 14. ctx.fillRect(x+143,y+37,10,10); 15. 16. // Nose 17. ctx.fillStyle='black'; 18. ctx.fillRect(x+90,y+70,20,80); 19. 20. // Mouth 21. ctx.fillStyle='purple'; 22. ctx.fillRect(x+60,y+165,80,20); 23. }
As you can see, the code uses the same technique, becomes less and less readable. The Xs and Ys at the beginning of each call makes understanding the code harder, etc. However, there is a way to simplify this => 2D geometric transformations!
The idea behind 2D transformations is that instead of modifying all the coordinates passed as parameters to each call to drawing methods like fillRect(…), we will keep all the drawing code "as is". For example, if the monster of our previous example was drawn at (0, 0), we could just translate (or rotate, or scale) the original coordinate system.
Let’s take a piece of code that draws something corresponding to the original coordinate system, located at the top left corner of the canvas:
1. function drawMonster(x, y) { 2. // head 3. ctx.fillStyle='lightgreen'; 4. ctx.fillRect(0,0,200,200); 5. 6. // eyes 7. ctx.fillStyle='red'; 8. ctx.fillRect(35,30,20,20); 9. ctx.fillRect(140,30,20,20); 10. 11. // interior of eye 12. ctx.fillStyle='yellow'; 13. ctx.fillRect(43,37,10,10); 14. ctx.fillRect(143,37,10,10); 15. 16. // Nose 17. ctx.fillStyle='black'; 18. ctx.fillRect(90,70,20,80); 19. 20. // Mouth 21. ctx.fillStyle='purple'; 22. ctx.fillRect(60,165,80,20); 23. 24. // coordinate system at (0, 0) 25. drawArrow(ctx, 0, 0, 100, 0, 10, 'red'); 26. drawArrow(ctx, 0, 0, 0, 100, 10, 'red'); 27. }
This code is the just the same as in the previous example except that we removed all Xs and Yx in the code. We also added at the end (lines 25-26) two lines of code that draw the coordinate system. The drawArrow(startX, startY, endX, endY, width, color) function is a utility function that we will present later. You can see it in the JavaScript source code of the pen below:
// Borrowed and adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ //variables to be used when creating the arrow var headlen = 10; var angle = Math.atan2(toy-fromy,tox-fromx); ctx.save(); ctx.strokeStyle = color; //starting path of the arrow from the start square to the end square and drawing the stroke ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.lineWidth = arrowWidth; ctx.stroke(); //starting a new path from the head of the arrow to one of the sides of the point ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //path from the side point of the arrow, to the other side point ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7)); //path from the side point back to the tip of the arrow, and then again to the opposite side point ctx.lineTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //draws the paths created above ctx.stroke(); ctx.restore(); }
<!DOCTYPE html> <html lang="en"> <head> <title>Monster's head drawn with 2D transformations</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw, try to change these values drawMonster(0, 0); } function drawMonster(x, y) { // head ctx.fillStyle='lightgreen'; ctx.fillRect(0,0,200,200); // eyes ctx.fillStyle='red'; ctx.fillRect(35,30,20,20); ctx.fillRect(140,30,20,20); // interior of eye ctx.fillStyle='yellow'; ctx.fillRect(43,37,10,10); ctx.fillRect(143,37,10,10); // Nose ctx.fillStyle='black'; ctx.fillRect(90,70,20,80); // Mouth ctx.fillStyle='purple'; ctx.fillRect(60,165,80,20); // coordinate system at (0, 0) drawArrow(ctx, 0, 0, 100, 0, 10, 'red'); drawArrow(ctx, 0, 0, 0, 100, 10, 'red'); } </script>
Note that the X and Y parameters are useless for now…
Translation using ctx.translate(offsetX, offsetY)
Now, instead of simply calling drawMonster(0, 0), we will call first ctx.translate(100, 100), and look at the result below:
// Borrowed and adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ //variables to be used when creating the arrow var headlen = 10; var angle = Math.atan2(toy-fromy,tox-fromx); ctx.save(); ctx.strokeStyle = color; //starting path of the arrow from the start square to the end square and drawing the stroke ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.lineWidth = arrowWidth; ctx.stroke(); //starting a new path from the head of the arrow to one of the sides of the point ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //path from the side point of the arrow, to the other side point ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7)); //path from the side point back to the tip of the arrow, and then again to the opposite side point ctx.lineTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //draws the paths created above ctx.stroke(); ctx.restore(); }
<!DOCTYPE html> <html lang="en"> <head> <title>Translated monster's head</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw, try to change these values ctx.translate(100, 100); drawMonster(0, 0); } function drawMonster(x, y) { // head ctx.fillStyle='lightgreen'; ctx.fillRect(0,0,200,200); // eyes ctx.fillStyle='red'; ctx.fillRect(35,30,20,20); ctx.fillRect(140,30,20,20); // interior of eye ctx.fillStyle='yellow'; ctx.fillRect(43,37,10,10); ctx.fillRect(143,37,10,10); // Nose ctx.fillStyle='black'; ctx.fillRect(90,70,20,80); // Mouth ctx.fillStyle='purple'; ctx.fillRect(60,165,80,20); // coordinate system at (0, 0) drawArrow(ctx, 0, 0, 100, 0, 10, 'red'); drawArrow(ctx, 0, 0, 0, 100, 10, 'red'); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
ctx.translate(100, 100); drawMonster(0, 0);
Line 1 changes the position of the coordinate system, line 2 draws a monster in the new translated coordinate system. All subsequent calls to drawing methods will be affected and will work in this new system too.
Here is the previous example, but this time we translated the coordinate system, then rotated it with an angle equal to PI/4 , then we scaled it so that units are half as big:
// Borrowed and adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ //variables to be used when creating the arrow var headlen = 10; var angle = Math.atan2(toy-fromy,tox-fromx); ctx.save(); ctx.strokeStyle = color; //starting path of the arrow from the start square to the end square and drawing the stroke ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.lineWidth = arrowWidth; ctx.stroke(); //starting a new path from the head of the arrow to one of the sides of the point ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //path from the side point of the arrow, to the other side point ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7)); //path from the side point back to the tip of the arrow, and then again to the opposite side point ctx.lineTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //draws the paths created above ctx.stroke(); ctx.restore(); }
<!DOCTYPE html> <html lang="en"> <head> <title>Translated, rotated, and scaled monster's head</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw, try to change these values ctx.translate(100, 100); ctx.rotate(Math.PI/4); ctx.scale(0.5, 0.5); drawMonster(0, 0); } function drawMonster(x, y) { // head ctx.fillStyle='lightgreen'; ctx.fillRect(0,0,200,200); // eyes ctx.fillStyle='red'; ctx.fillRect(35,30,20,20); ctx.fillRect(140,30,20,20); // interior of eye ctx.fillStyle='yellow'; ctx.fillRect(43,37,10,10); ctx.fillRect(143,37,10,10); // Nose ctx.fillStyle='black'; ctx.fillRect(90,70,20,80); // Mouth ctx.fillStyle='purple'; ctx.fillRect(60,165,80,20); // coordinate system at (0, 0) drawArrow(ctx, 0, 0, 100, 0, 10, 'red'); drawArrow(ctx, 0, 0, 0, 100, 10, 'red'); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
ctx.translate(100, 100); ctx.rotate(Math.PI/4); ctx.scale(0.5, 0.5); drawMonster(0, 0);
If we draw two shapes at two different positions, they will be relative to this new coordinate system.
// Borrowed and adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ //variables to be used when creating the arrow var headlen = 10; var angle = Math.atan2(toy-fromy,tox-fromx); ctx.save(); ctx.strokeStyle = color; //starting path of the arrow from the start square to the end square and drawing the stroke ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.lineWidth = arrowWidth; ctx.stroke(); //starting a new path from the head of the arrow to one of the sides of the point ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //path from the side point of the arrow, to the other side point ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7)); //path from the side point back to the tip of the arrow, and then again to the opposite side point ctx.lineTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //draws the paths created above ctx.stroke(); ctx.restore(); }
<!DOCTYPE html> <html lang="en"> <head> <title>2D transformation</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw, try to change these values ctx.translate(100, 100); ctx.rotate(Math.PI/4); ctx.scale(0.5, 0.5); // Draw the monster at (0, 0) drawMonster(0, 0); // draw a filled rectagle at (250, 0) ctx.fillRect(250, 0, 100, 100); drawCoordinateSystem('red', 10); } function drawMonster(x, y) { // head ctx.fillStyle='lightgreen'; ctx.fillRect(0,0,200,200); // eyes ctx.fillStyle='red'; ctx.fillRect(35,30,20,20); ctx.fillRect(140,30,20,20); // interior of eye ctx.fillStyle='yellow'; ctx.fillRect(43,37,10,10); ctx.fillRect(143,37,10,10); // Nose ctx.fillStyle='black'; ctx.fillRect(90,70,20,80); // Mouth ctx.fillStyle='purple'; ctx.fillRect(60,165,80,20); } function drawCoordinateSystem(color, width) { // coordinate system at (0, 0) drawArrow(ctx, 0, 0, 100, 0, width, color); drawArrow(ctx, 0, 0, 0, 100, width, color); } </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
ctx.translate(100, 100); ctx.rotate(Math.PI/4); ctx.scale(0.5, 0.5); // Draw the monster at (0, 0) drawMonster(0, 0); // Draw a filled rectagle at (250, 0) ctx.fillRect(250, 0, 100, 100);
Aha, this is a very interesting question… the answer is in the next page!
There are two methods for saving and restoring the context properties: ctx.save() and ctx.restore().
What will be saved: fillStyle and strokeStyle, lineWidth, the previous coordinate system, etc. Meaning that ALL properties that affect drawing!
A call to ctx.save() will probably save the context property values in a hardware register on your graphics card. Multiple contexts can be saved consecutively and restored.
Multiple contexts can be backed up consecutively and restored. Contexts saved will be stacked, the last one that has been saved will be restored by the next call to restore(), so it is very important to have one restore for each save.
Best practice: save the context at the beginning of any function that changes the context, restore it at the end of the function!
// Borrowed and adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ //variables to be used when creating the arrow var headlen = 10; var angle = Math.atan2(toy-fromy,tox-fromx); ctx.save(); ctx.strokeStyle = color; //starting path of the arrow from the start square to the end square and drawing the stroke ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.lineWidth = arrowWidth; ctx.stroke(); //starting a new path from the head of the arrow to one of the sides of the point ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //path from the side point of the arrow, to the other side point ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7)); //path from the side point back to the tip of the arrow, and then again to the opposite side point ctx.lineTo(tox, toy); ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7)); //draws the paths created above ctx.stroke(); ctx.restore(); }
<!DOCTYPE html> <html lang="en"> <head> <title>Saving and restoring the context</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; function init() { // This function is called after the page is loaded // 1 - Get the canvas canvas = document.getElementById('myCanvas'); // 2 - Get the context ctx=canvas.getContext('2d'); // 3 - we can draw, try to change these values drawMonster(200, 100, Math.PI/4, 'lightGreen', 'yellow'); // draw another rectangle at (0, 0), you will see it's black // (default color), and at the top left corner of the canvas // (original position of the coordinate system) ctx.fillRect(0, 0, 80, 80); } /* function init() */ function drawMonster(x, y, angle, headColor, eyeColor) { // GOOD PRACTICE : SAVE CONTEXT AND RESTORE IT AT THE END ctx.save(); // Moves the coordinate system so that the monster is drawn // at position (x, y) ctx.translate(x, y); ctx.rotate(angle) // head ctx.fillStyle=headColor; ctx.fillRect(0,0,200,200); // eyes ctx.fillStyle='red'; ctx.fillRect(35,30,20,20); ctx.fillRect(140,30,20,20); // interior of eye ctx.fillStyle=eyeColor; ctx.fillRect(43,37,10,10); ctx.fillRect(143,37,10,10); // Nose ctx.fillStyle='black'; ctx.fillRect(90,70,20,80); // Mouth ctx.fillStyle='purple'; ctx.fillRect(60,165,80,20); // coordinate system at (0, 0) drawArrow(ctx, 0, 0, 100, 0, 10, 'red'); drawArrow(ctx, 0, 0, 0, 100, 10, 'red'); // GOOD PRACTICE ! ctx.restore(); } /* function drawMonster () */ </script> </head> <body onload="init();"> <canvas id="myCanvas" width="400" height="400"> Your browser does not support the canvas tag. </canvas> </body> </html>
We slightly modified the function that draws the monster:
Source code extract of this function: notice at lines 3 and 26 how we save/restore the context at the beginning/end. Right after saving the context, we modify the coordinate system (lines 7-8). The rest of the code is nearly the same as in the last version of the monster example.
1. function drawMonster(x, y, angle, headColor, eyeColor) { 2. // BEST PRACTICE: SAVE CONTEXT AND RESTORE IT AT THE END 3. ctx.save(); 4. 5. // Moves the coordinate system so that the monster is drawn 6. // at position (x, y) 7. ctx.translate(x, y); 8. ctx.rotate(angle); 9. 10. // head 11. ctx.fillStyle=headColor; 12. ctx.fillRect(0,0,200,200); 13. 14. // eyes 15. ctx.fillStyle='red'; 16. ctx.fillRect(35,30,20,20); 17. ctx.fillRect(140,30,20,20); 18. 19. // interior of eye 20. ctx.fillStyle=eyeColor; 21. ctx.fillRect(43,37,10,10); 22. ctx.fillRect(143,37,10,10); 23. 24. ... 25. // BEST PRACTICE! 26. ctx.restore(); 27. }
In the previous sections, we learned how to draw filled or wireframe rectangles.
As soon as the ctx.strokeRect(x, y, width, height) or the ctx.fillRect(x, y, width, height) method is called, a rectangle is indeed drawn immediately in the canvas.
While drawing rectangles with strokeRect or fillRect, drawing text or drawing images, all these shapes will be drawn in immediate mode.
Another mode called “path mode” or “buffered mode” will be seen later in this course, which will be useful for drawing lines, curves, arcs, and also rectangles. Rectangles are the only shapes that have methods for drawing them immediately and also other methods for drawing them in “path/buffered mode”.
Let’s give an example that draws several rectangles, filled or wireframe, with different colors and line widths:
<!DOCTYPE html> <html lang="en"> <head> <title>Immediate mode</title> <meta charset="utf-8"/> <style> #myCanvas { border: 1px solid black; } </style> <script> var canvas, ctx; window.onload = function () { canvas = document.getElementById('myCanvas'); ctx = canvas.getContext('2d'); // black rectangle, default color (black) ctx.fillRect(10, 10, 100, 100); // outlined rectangle, default color ctx.strokeRect(150, 10, 100, 100); // outlined rectangle filled in red, outline blue ctx.fillStyle = 'red'; ctx.strokeStyle = 'lightBlue'; ctx.lineWidth = 10; ctx.fillRect(100, 150, 150, 150); ctx.strokeRect(100, 150, 150, 150); // A function to automatize previous drawing var angle = Math.PI / 10; drawFilledRectangle(300, 150, 150, 150, 'pink', 'green', 10, angle); drawFilledRectangle(300, 150, 150, 150, 'yellow', 'purple', 10, angle + 0.5); }; function drawFilledRectangle(x, y, w, h, fillColor, strokeColor, lw, angle) { // GOOD PRACTICE : save if the function change the context or coordinate // system ctx.save(); // position coordinate system ctx.translate(x, y); ctx.rotate(angle); // set colors, line width... ctx.lineWidth = lw; ctx.fillStyle = fillColor; ctx.strokeStyle = strokeColor; // draw at 0, 0 as we translated the coordinate // system already ctx.fillRect(0, 0, w, h); ctx.strokeRect(0, 0, w, h); // GOOD PRACTICE : a restore for a save! ctx.restore(); } </script> </head> <body> <canvas id="myCanvas" width="578" height="400"> </canvas> </body> </html>
The canvas API provides two main methods for drawing text: ctx.strokeText(message, x, y) and ctx.fillText(message, x, y). It also provides a set of context properties for setting the character font and style, for laying out the text, etc.
Look at the example below, and change the position where the text is drawn, change font attributes, etc.:
<!DOCTYPE html> <html> <head> <title>Drawing text</title> <meta charset="utf-8"/> </head> <body> <canvas id="myCanvas" width=500 height=300>Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var context=canvas.getContext('2d'); context.font = "60pt Calibri"; context.lineWidth = 3; context.strokeStyle = "blue"; context.fillStyle = "red"; context.fillText("Hello World!", 10, 100); context.strokeText("Hello World!", 10, 100); </script> </body> </html>
1. context.font = "60pt Calibri"; 2. // .. set color, lineWidth, shadow etc. 3. 4. // 10, 10 is the start of the baseline, bottom of left leg of the "H" in the 5. // "Hello World" example. 6. context.fillText("Hello World!", 10, 10); 7. // Or 8. context.strokeText("Hello World!", 10, 10);
It is possible to draw text in a canvas using the font property of the context to specify the font style (plain, bold, italic), the size, and the font name. Other properties such as strokeStyle or fillStyle, as well as other properties that are detailed in the next pages, are also going to be taken into account.
The font property accepts values like: font-style, font-weight, font-size, font-face.
The fillText(message, x, y) or strokeText(message, x, y) methods from the context will actually draw a text message at the origin of the baseline position. In the “Hello World” example, this is located at the bottom of the left leg of the “H”.
There is a fourth optional parameter maxWidth that forces the text to fit into a given width, distorting it if necessary:
context.strokeText("Hello World!", x, y [, maxWidth]); context.fillText("Hello World!", x, y [, maxWidth]);
<!DOCTYPE html> <html> <head> <title>Drawing text: the maxWidth property</title> <meta charset="utf-8"/> </head> <body> <canvas id="myCanvas" width=500 height=300>Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var context=canvas.getContext('2d'); context.font = "60pt Calibri"; context.lineWidth = 3; context.strokeStyle = "blue"; context.fillStyle = "red"; context.fillText("Hello World!", 10, 100); context.strokeText("Hello World!", 10, 100); // Draw text with constrained width context.fillText("Hello World!", 10, 160, 250); context.strokeText("Hello World!", 10, 160, 250); context.fillText("Hello World!", 10, 220, 150); context.strokeText("Hello World!", 10, 220, 150); </script> </body> </html>
1. ... 2. context.font = "60pt Calibri"; 3. context.lineWidth = 3; 4. context.strokeStyle = "blue"; 5. context.fillStyle = "red"; 6. 7. context.fillText("Hello World!", 10, 100); 8. context.strokeText("Hello World!", 10, 100); 9. 10. // Draw text with constrained width of 250 pixels 11. context.fillText("Hello World!", 10, 160, 250); 12. context.strokeText("Hello World!", 10, 160, 250); 13. 14. // Constrain width to 150 pixels 15. context.fillText("Hello World!", 10, 220, 150); 16. context.strokeText("Hello World!", 10, 220, 150);
The ctx.measureText() method can be used to get the current width in pixels of a given text, taking into account the diverse properties involved such as font, size, shadow, lineWidth, etc.
<!DOCTYPE html> <html> <head> <title>Drawing text: the ctx.measureText() method</title> <meta charset="utf-8"/> </head> <body> <canvas id="myCanvas" width=500 height=300>Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var context=canvas.getContext('2d'); context.font = "60pt Calibri"; context.lineWidth = 3; context.strokeStyle = "blue"; context.fillStyle = "red"; context.fillText("Hello World!", 10, 100); context.strokeText("Hello World!", 10, 100); var textMetrics = context.measureText("Hello World!"); var width = textMetrics.width; context.font = "20pt Arial"; context.fillText("Width of previous text: " + width + "pixels", 10, 150); // Draw the baseline of the given width context.moveTo(10, 100); context.lineTo(width+10, 100); context.stroke(); </script> </body> </html>
1. context.font = "60pt Calibri"; 2. context.lineWidth = 3; 3. context.strokeStyle = "blue"; 4. context.fillStyle = "red"; 5. 6. context.fillText("Hello World!", 10, 100); 7. context.strokeText("Hello World!", 10, 100); 8. 9. var textMetrics = context.measureText("Hello World!"); 10. var width = textMetrics.width; 11. 12. // Draw a text that displays the width of the previous drawn text 13. context.font = "20pt Arial"; 14. context.fillText("Width of previous text: " + width + "pixels", 10, 150); 15. 16. // Draw the baseline of the given width 17. context.moveTo(10, 100); 18. context.lineTo(width+10, 100); 19. context.stroke();
The textBaseline property of the context is used to specify the different ways one can position the baseline of a given text:
<!DOCTYPE html> <html> <head> <title>Drawing text: the ctx.textbaseline property</title> <meta charset="utf-8"/> </head> <body> <canvas id="myCanvas" width=500 height=300>Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var context=canvas.getContext('2d'); context.strokeStyle = "#000000"; context.lineWidth = 1; context.beginPath(); context.moveTo( 0, 75); context.lineTo(500, 75); context.stroke(); context.closePath(); context.font = "16px Verdana"; context.fillStyle = "#000000"; context.textBaseline = "top"; context.fillText("top", 0, 75); context.textBaseline = "hanging"; context.fillText("hanging", 40, 75); context.textBaseline = "middle"; context.fillText("middle", 120, 75); context.textBaseline = "alphabetic"; context.fillText("alphabetic", 200, 75); context.textBaseline = "ideographic"; context.fillText("ideographic", 300, 75); context.textBaseline = "bottom"; context.fillText("bottom-glyph", 400, 75); </script> </body> </html>
The example above shows the different possible values for this property and the corresponding results. The default value is "alphabetic" and corresponds to what has been used in the previous "Hello World" example.
<div data-align="center"> <table> <colgroup> <col style="width: 30%" /> <col style="width: 69%" /> </colgroup> <thead> <tr class="header"> <th>textBaseline property</th> <th>Description</th> </tr> </thead> <tbody> <tr class="odd"> <td>top</td> <td>The text is aligned based on the top of the tallest glyph in the text.</td> </tr> <tr class="even"> <td>hanging</td> <td>The text is aligned based on the line the text seems to hang from. This is almost identical to top, and in many cases, you cannot see the difference.</td> </tr> <tr class="odd"> <td>middle</td> <td>The text is aligned according to the middle of the text.</td> </tr> <tr class="even"> <td>alphabetic</td> <td>The bottom of vertically oriented glyphs, e.g. western alphabet like the Latin.</td> </tr> <tr class="odd"> <td>ideographic</td> <td>The bottom of horizontally oriented glyphs.</td> </tr> <tr class="even"> <td>bottom</td> <td>The text is aligned based on the bottom of the glyph in the text, that extends furthest down in the text.</td> </tr> </tbody> </table> </div>
Typical use (taken from the example above):
1. context.textBaseline = "top"; 2. context.fillText("top", 0, 75); 3. context.textBaseline = "hanging"; 4. context.fillText("hanging", 40, 75); 5. context.textBaseline = "middle"; 6. context.fillText("middle", 120, 75);
The textAlign property of the context tells how the x parameter will be used when calling strokeText("some text", x, y) and fillText("some text", x, y). For example, with textAlign=“center”, the x parameter gives the position of the vertical center of the text, while in textAlign=“right”, x corresponds to the rightmost position of the text.
<!DOCTYPE html> <html> <head> <title>Drawing text: the textAlign property</title> <meta charset="utf-8"/> </head> <body> <canvas id="myCanvas" width=500 height=120>Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var context=canvas.getContext('2d'); context.stokeStyle = "#000000"; context.lineWidth = 1; context.beginPath(); context.moveTo( 250, 0); context.lineTo( 250, 130); context.stroke(); context.closePath(); context.font = "16px Verdana"; context.fillStyle = "#000000"; context.textAlign = "center"; context.fillText("center", 250, 20); context.textAlign = "start"; context.fillText("start", 250, 40); context.textAlign = "end"; context.fillText("end", 250, 60); context.textAlign = "left"; context.fillText("left", 250, 80); context.textAlign = "right"; context.fillText("right", 250, 100); </script> </body> </html>
1. context.textAlign = "center"; 2. context.fillText("center", 250, 20); 3. context.textAlign = "start"; 4. context.fillText("start", 250, 40); 5. context.textAlign = "end"; 6. context.fillText("end", 250, 60); 7. context.textAlign = "left"; 8. context.fillText("left", 250, 80); 9. context.textAlign = "right"; 10. context.fillText("right", 250, 100);
Working with images is rather simple, except that we need the images to be fully loaded into memory before drawing them. Loading images is an asynchronous process we need to take care of. Working with multiple images might also be difficult for beginners. We present a multiple image loader later on in this course.
Let’s say it once again: To use an image in a canvas, make sure that the image has been loaded by the Web browser before drawing it!
It is also possible to draw images from a video stream, images corresponding to another canvas content, or images that are defined by <img> HTML elements in the page. We will see that as well in the following parts of this chapter.
But let’s start with a basic example!
1. <!DOCTYPE HTML> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Simple image drawing in a canvas</title> 6. <script> 7. window.onload = function () { 8. // Necessity to run this code only after the web page has been loaded. 9. var canvas = document.getElementById("myCanvas"); 10. var context = canvas.getContext("2d"); 11. var imageObj = new Image(); 12. // Callback function called by the imageObj.src = .... line 13. //located after this function 14. imageObj.onload = function () { 15. // Draw the image only when we have the guarantee 16. // that it has been loaded 17. context.drawImage(imageObj, 0, 0); 18. }; 19. 20. // Calls the imageObj.onload function asynchronously 21. imageObj.src = 22. "https://www.w3.org/html/logo/downloads/HTML5_Logo_512.png"; 23. }; 24. </script> 25. </head> 26. <body> 27. <canvas id="myCanvas" width="512" height="512"></canvas> 28. </body> 29. </html>
This example illustrates the use of the different variants of the drawImage method:
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"/> <title>Drawing images: drawImage variants</title> <script> window.onload = function() { var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); var imageObj = new Image(); imageObj.onload = function() { // Try commenting/uncommenting the following lines to see the // effect of the different drawImage variants // Original, big image // context.drawImage(imageObj, 0, 10); // Original image drawn with size = 100x100 pixels context.drawImage(imageObj, 0, 10, 100, 100); // with size = 150x150 context.drawImage(imageObj, 80, 10, 150, 150); // with size = 200x200 context.drawImage(imageObj, 210, 10, 200, 200); // draw the sub image at 0, 0, width = 512, height = 100 // at position 100, 250, with a width of 256 and a height of 50 context.drawImage(imageObj, 0, 0, 512, 100, 100, 250, 256, 50); }; imageObj.src = "https://www.w3.org/html/logo/downloads/HTML5_Logo_512.png"; }; </script> </head> <body> <canvas id="myCanvas" width="512" height="512"></canvas> </body> </html>
#myCanvas { border:1px solid black; }
1. var imageObj = new Image(); 2. 3. imageObj.onload = function() { 4. // try commenting/uncommenting the following lines to see the 5. // effect of the different drawImage variants 6. 7. // original, big image 8. // context.drawImage(imageObj, 0, 10); 9. 10. // original image drawn with size = 100x100 pixels 11. context.drawImage(imageObj, 0, 10, 100, 100); 12. // with size = 150x150 13. context.drawImage(imageObj, 80, 10, 150, 150); 14. // with size = 200x200 15. context.drawImage(imageObj, 210, 10, 200, 200); 16. 17. // draw the sub image at 0, 0, width = 512, height = 100 18. // at position 100, 250, with a width of 256 and a height of 50 19. context.drawImage(imageObj, 0, 0, 512, 100, 100, 250, 256, 50); 20. }; 21. imageObj.src = "https://www.w3.org/html/logo/downloads/HTML5_Logo_512.png"; 22. };
Sometimes, you may want to draw an image that is already declared in the HTML document as an <img src="..."> element. Remember that when you add an <img> in the document, the browser starts downloading it in background.
1. <body> 2. <canvas id="myCanvas" width="512" height="512"></canvas> 3. <p>Original image as an <img> element:</p> 4. <b><img id="logo"</b> 5. <b>src="https://fc07.deviantart.net/fs70/f/2013/149/b/8/texture_85_by_voyager168-d670m68.jpg"></b> 6. <script> 7. canvas = document.getElementById("myCanvas"); 8. var ctx = canvas.getContext("2d"); 9. var logo = document.querySelector("#logo"); 10. <b>ctx.drawImage(logo, 0, 0, 100, 100);</b> 11. </script> 12. </body>
Although you will find many examples on the Web that do it this way, they will only work most of the time with small images, or with images that are in the browser’s cache. Remember that you cannot draw an image that has not been fully loaded!
If you try to draw an image that is not loaded or partially loaded, you will have unexpected results!
Best practice: only draw an image that is fully loaded, use the onload callback! The right way to do this is shown in this online example, that starts drawing only from the onload callback function:
#myCanvas { border:1px solid black; }
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"/> <title>Drawing an image with <img></title> <script> var canvas, context, imageObj; window.onload = function() { canvas = document.getElementById("myCanvas"); context = canvas.getContext("2d"); imageObj = document.querySelector("#logo"); drawAllImages(); }; function drawAllImages() { console.log("image is already loaded, we draw it!"); // Original image drawn with size = 100x100 pixels context.drawImage(imageObj, 0, 10, 100, 100); // with size = 150x150 context.drawImage(imageObj, 80, 10, 150, 150); // with size = 200x200 context.drawImage(imageObj, 210, 10, 200, 200); // draw the sub image at 0, 0, width = 512, height = 100 // at position 100, 250, with a width of 256 and a height of 50 context.drawImage(imageObj, 0, 0, 512, 100, 100, 250, 256, 50); } </script> </head> <body> <p>A canvas with an image that is further in the page, loaded by the <img src=...> tag. This is not the recommended way to load images, except if the image is already in your page. Use the onload callback to be sure that the image is in the page. </p> <canvas id="myCanvas" width="512" height="512"></canvas> <p>Original image is an <img> element: </p> <img id="logo" src="https://www.w3.org/html/logo/downloads/HTML5_Logo_512.png" alt="html5 logo"> </body> </html>
With large image files, this will not break nor produce unexpected results:
#myCanvas { border:1px solid black; }
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"/> <title>Using a large image</title> <script> let canvas, context; window.onload = () => { canvas = document.getElementById("myCanvas"); context = canvas.getContext("2d"); draw(); }; function draw() { var imageObj = document.querySelector("#logo"); console.log("Image has been loaded, let's draw it!"); // Original image drawn with size = 100x100 pixels context.drawImage(imageObj, 0, 10, 100, 100); // with size = 150x150 context.drawImage(imageObj, 80, 10, 150, 150); // with size = 200x200 context.drawImage(imageObj, 210, 10, 200, 200); // draw the sub image at 0, 0, width = 512, height = 100 // at position 100, 250, with a width of 256 and a height of 50 context.drawImage(imageObj, 0, 0, 512, 100, 100, 250, 256, 50); } </script> </head> <body> <p>It is possible that the drawing in the canvas below appears only after a few seconds, because the image is very large!</p> <canvas id="myCanvas" width="512" height="512"></canvas> <p>Very large original image, declared as an <img> element: </p> <img id="logo" alt="logo" src="https://mainline.i3s.unice.fr/mooc/texture_85_by_voyager168-d670m68.jpg" width="512" height="512"> </body> </html>
The DOM Level 2 Events specification says: "The load event occurs when the DOM implementation finishes loading all content within a document, all frames within a FRAMESET, or an OBJECT element."
The drawImage(…) function can take a video element as its first parameter. The image that will be drawn is the one currently played by the video stream. This can be done at video frequency on most modern computers or mobile devices.
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="utf-8"/> <title>Drawing images from a video stream</title> <style> body { margin: 10px; padding: 0px; } #myCanvas { border: 10px solid red; } </style> <script> var video; var canvas, ctx; var angle = 0; function init() { video = document.getElementById('sourcevid'); canvas = document.getElementById('myCanvas'); ctx = canvas.getContext('2d'); setInterval("processFrame()", 25); } function processFrame() { // Uncomment next line for drawing 100% copy of the video content //ctx.drawImage(video, 0, 0); // Comment these 4 lines if you uncommented the previous one ctx.drawImage(video, 0, 0, 320, 180); drawRotatingVideo(480, 90); ctx.drawImage(video, 0, 180, 320, 180); ctx.drawImage(video, 320, 180, 320, 180); } function drawRotatingVideo(x, y) { // Clear thze zone at the top right quarter of the canvas ctx.clearRect(320, 0, 320, 180); // We are going to change the coordinate system, save the context ! ctx.save(); // translate, rotate and recenter the image at its "real" center, //not the top left corner ctx.translate(x, y); ctx.rotate(angle += 0.01); ctx.translate(-80, -45); ctx.drawImage(video, 0, 0, 160, 90); // restore the context ctx.restore(); } </script> </head> <body onload="init()" > <p>This is a <code><video></code> element: </p> <video id="sourcevid" autoplay loop> <source src="https://mainline.i3s.unice.fr/mooc/BigBuckBunny_640x360.mp4" type="video/mp4" /> <source src="https://mainline.i3s.unice.fr/mooc/BigBuckBunny_640x360.ogv" type="video/ogg"/> </video> <p>This is a <code><canvas></code> element: </p> <canvas id="myCanvas" width="620" height="360"></canvas> </body> </html>
This example shows:
1. <script> 2. var video; 3. var canvas, ctx; 4. var angle = 0; 5. 6. function init() { 7. video = document.getElementById('sourcevid'); 8. canvas = document.getElementById('myCanvas'); 9. ctx = canvas.getContext('2d'); 10. 11. setInterval("processFrame()", 25); // call processFrame each 25ms 12. } 13. 14. function processFrame() { 15. ctx.drawImage(video, 0, 0, 320, 180); 16. drawRotatingVideo(480, 90); 17. ctx.drawImage(video, 0, 180, 320, 180); 18. ctx.drawImage(video, 320, 180, 320, 180); 19. } 20. 21. function drawRotatingVideo(x, y) { 22. // Clear the zone at the top right quarter of the canvas 23. ctx.clearRect(320, 0, 320, 180); 24. 25. // We are going to change the coordinate system, save the context! 26. ctx.save(); 27. // translate, rotate and recenter the image at its "real" center, 28. //not the top left corner 29. ctx.translate(x, y); 30. ctx.rotate(angle += 0.01); // rotate and increment the current angle 31. ctx.translate(-80, -45); 32. 33. ctx.drawImage(video, 0, 0, 160, 90); 34. 35. // restore the context 36. ctx.restore(); 37. } 38. </script> 39. </head> 40. 41. <body onload="init()" > 42. <p>This is a <video><video> element: </p> 43. <video id="sourcevid" autoplay="true" loop="true"> 44. <source src="https://mainline.i3s.unice.fr/mooc/BigBuckBunny_640x360.mp4" 45. type="video/mp4" /> 46. <source src="https://mainline.i3s.unice.fr/mooc/BigBuckBunny_640x360.ogv" 47. type="video/ogg"/> 48. </video> 49. <p>This is a <canvas> element: </p> 50. <canvas id="myCanvas" width="620" height="360"></canvas> 51. </body>
As a reminder: an immediate mode means “executing a call to a drawing method means immediately drawing in the canvas”. The drawing appears as soon as the design instruction is executed.
Here is an example that draws 1000 random rectangles in a canvas, using immediate mode rectangle drawing calls:
var canvas, ctx, w, h; function init() { canvas = document.getElementById('myCanvas'); ctx = canvas.getContext('2d'); w = canvas.width; h = canvas.height; console.time("time to draw"); for(var i=0; i < 1000; i++) { var x = Math.random() * w; var y = Math.random() * h; var width = Math.random() * w; var height = Math.random() * h; ctx.strokeRect(x, y, width, height); } console.timeEnd("time to draw"); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Canvas</title> </head> <body onload = init();> <canvas id="myCanvas" width="400" height =400> Your browser does not support the canvas tag.</canvas> </body> </html>
1. var canvas, ctx, w, h; 2. 3. function init() { 4. canvas = document.getElementById('myCanvas'); 5. ctx = canvas.getContext('2d'); 6. 7. w = canvas.width; 8. h = canvas.height; 9. 10. console.time("time to draw"); 11. 12. for(var i=0; i < 1000; i++) { 13. var x = Math.random() * w; 14. var y = Math.random() * h; 15. var width = Math.random() * w; 16. var height = Math.random() * h; 17. 18. ctx.strokeRect(x, y, width, height); 19. } 20. console.timeEnd("time to draw"); 21. }
Lines 12-18 draw 1000 rectangles of random sizes in immediate mode. We also measure the time using the usual console.time(name_of_timer) and console.timeEnd(name_of_timer) that will write in the browser console the time elapsed. Note that console.time(…) and console.timeEnd(…) display results only in the browser’s console, not in the JSBin console.
On a Mac Book Pro from 2015, the result is an average time of 4.034ms for drawing all these rectangles:
There is another drawing mode called “path drawing mode” where you first send drawing orders to the graphics processor, and these orders are stored in a buffer. Then you call methods to draw the whole buffer at once. There are also methods to erase the buffer’s content.
Path drawing mode allows parallelism: if you need to draw 10,000 rectangles, it’s better to store the orders in the graphics card, then execute the drawing all at once, rather than doing 10,000 immediate calls to strokeRect(…) for example. With the buffered mode, the Graphic Processing Unit (GPU) of the graphics card hardware will be able to parallelize the computations (modern graphics cards can execute hundreds/thousands of things in parallel).
Same example as before, this time using the buffered mode for drawing rectangles:
var canvas, ctx, w, h; function init() { canvas = document.getElementById('myCanvas'); ctx = canvas.getContext('2d'); w = canvas.width; h = canvas.height; console.time("time to draw"); for(var i=0; i < 1000; i++) { var x = Math.random() * w; var y = Math.random() * h; var width = Math.random() * w; var height = Math.random() * h; ctx.rect(x, y, width, height); } ctx.stroke(); console.timeEnd("time to draw"); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Canvas</title> </head> <body onload = init();> <canvas id="myCanvas" width="400" height =400> Your browser does not support the canvas tag.</canvas> </body> </html>
Extract from source code (the part that draws the rectangles):
1. for(var i=0; i < 1000; i++) { 2. var x = Math.random() * w; 3. var y = Math.random() * h; 4. var width = Math.random() * w; 5. var height = Math.random() * h; 6. ctx.rect(x, y, width, height); // store a rectangle in path/buffer 7. } 8. ctx.stroke(); // draws the whole buffer (the 1000 rectangles) at once
Instead of calling strokeRect(…) or fillRect(…), we just call the rect(…) method of the context (line 7). This is how we can delay the drawing of the rectangles. The 1000 rectangles are stored in a buffer in the hardware.
The call to ctx.stroke() (line 9) or to its sister method ctx.fill() will draw the entire buffer contents in fill or stroke mode.
And here is what the timer gives: a slightly faster execution time. Changing 1000 to 100,000 will give even larger differences.
Path mode is faster than immediate mode! We have now an average time of 3.1ms
A call to ctx.beginPath() will reset the buffer (empty its contents). We will see many more examples of using the path drawing mode in another further section.
1. // start a new buffer / path 2. ctx.beginPath(); 3. // all these orders are in a buffer/path 4. ctx.moveTo(10, 10); 5. ctx.lineTo(100, 100); 6. ctx.lineTo(150, 70); 7. // Draw the buffer 8. ctx.stroke();
Summary of path mode principles
Warning: you do not need to spend too much time on each part of this sub-section.
You do not need to memorize or learn by heart all the examples in the following pages. They are given as references. There will be no quizzes about curves as they are not often done “by hand”, but are generated by tools such as Adobe Illustrator or online generators.
Also, do not forget to use your favorite HTML5 canvas cheatsheet (provided before in the last section). You will find it very helpful when you start playing with the canvas.
We have been drawing rectangles so far.
Now let’s go a bit further by introducing the notion of “path drawing”. This approach uses the ctx.moveTo(x, y) method of the context, in conjunction with other drawing methods that end in “To”, such as ctx.lineTo(x, y).
This makes it easier to draw multiple connected lines. Consecutive calls to ctx.lineTo(x, y) will store in the path/buffer a set of connected lines that we will draw altogether by a single call to ctx.stroke() or ctx.fill().
Note the call to ctx.stroke() or ctx.fill() will use the current values of the strokeStyle or fillStyle properties. It is possible to call ctx.moveTo(x, y) in the middle of steps 1 through 5 in order to move the pen somewhere else without connecting to the last drawn line.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Drawing a grid</title> </head> <body> <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var ctx=canvas.getContext('2d'); // Vertical lines for (var x = 0.5; x < 500; x += 10) { ctx.moveTo(x, 0); ctx.lineTo(x, 375); } // Horizontal lines for (var y = 0.5; y < 375; y += 10) { ctx.moveTo(0, y); ctx.lineTo(500, y); } // Draw in blue ctx.strokeStyle = "#0000FF"; // Before the execution of the next line nothing as been drawn yet ! ctx.stroke(); </script> </body> </html>
1. var canvas=document.getElementById('myCanvas'); 2. var ctx=canvas.getContext('2d'); 3. 4. // Vertical lines 5. for (var x = 0.5; x < 500; x += 10) { 6. ctx.moveTo(x, 0); 7. ctx.lineTo(x, 375); 8. } 9. 10. // Horizontal lines 11. for (var y = 0.5; y < 375; y += 10) { 12. ctx.moveTo(0, y); 13. ctx.lineTo(500, y); 14. } 15. 16. // Draw in blue 17. ctx.strokeStyle = "#0000FF"; 18. 19. // Until the execution of the next line, nothing has been drawn! 20. ctx.stroke();
In this example, the entire grid is drawn during the execution of the last line of code, with the single call to ctx.stroke().
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Canvas</title> </head> <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var ctx=canvas.getContext('2d'); ctx.fillStyle='#FF0000'; ctx.fillRect(0,0,80,100); ctx.moveTo(0,0); ctx.lineTo(100, 100); ctx.lineTo(100,0); ctx.strokeStyle = "#0000FF"; ctx.stroke(); </script> </body> </html>
1. var canvas=document.getElementById('myCanvas'); 2. var ctx=canvas.getContext('2d'); 3. 4. // a filled rectangle in immediate mode 5. ctx.fillStyle='#FF0000'; 6. ctx.fillRect(0,0,80,100); 7. 8. // two consecutive lines in path mode 9. ctx.moveTo(0,0); 10. ctx.lineTo(100, 100); 11. ctx.lineTo(100,0); 12. 13. // draws only the two lines in wireframe mode 14. ctx.strokeStyle = "#0000FF"; 15. ctx.stroke();
This example shows that filled and wireframe shapes should be drawn differently (here a filled rectangle is drawn using a call to the fillRect(…) method while a wireframe set of connected lines is drawn using the stroke() method of the context).
Try this:
1. var canvas=document.getElementById('myCanvas'); 2. var ctx=canvas.getContext('2d'); 3. 4. // first part of the path 5. ctx.moveTo(20,20); 6. ctx.lineTo(100, 100); 7. ctx.lineTo(100,0); 8. 9. // second part of the path, moveTo(...) is used to "jump" to another place 10. ctx.moveTo(120,20); 11. ctx.lineTo(200, 100); 12. ctx.lineTo(200,0); 13. 14. // indicate stroke color + draw the path 15. ctx.strokeStyle = "#0000FF"; 16. ctx.stroke();
In this last example, we simply called the moveTo() method between each part of the path (lines 5 and 10). And we called stroke() (line 16) only once to draw the whole path.
Let’s look at the drawing from the last example of the previous section:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Drawing disconnected lines</title> </head> <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> <script type="text/javascript"> var canvas=document.getElementById('myCanvas'); var ctx=canvas.getContext('2d'); // first part of the path ctx.moveTo(20,20); ctx.lineTo(100, 100); ctx.lineTo(100,0); // second part of the path, moveTo(...) is used to "jump" to another place ctx.moveTo(120,20); ctx.lineTo(200, 100); ctx.lineTo(200,0); // indicate stroke color + draw the path ctx.strokeStyle = "#0000FF"; ctx.stroke(); </script> </body> </html>
Imagine that we would like to draw them with different styles and colors: the shape on the left will stay as it is now (blue, wireframe), while the shape on the right will be filled, colored in pink. Let’s look at how we can do this…
In this example, we will draw the two parts of the path with different styles: the first part in wireframe mode, and the second part in filled mode.
What we will try first is to call stroke() after the first half of the path, then call fill() after the second half of the path:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Drawing two paths with different styles: the wrong way!</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.getElementById('myCanvas'); 11. var ctx=canvas.getContext('2d'); 12. // first part of the path 13. ctx.moveTo(20,20); 14. ctx.lineTo(100, 100); 15. ctx.lineTo(100,0); 16. // indicate stroke color + draw first part of the path 17. ctx.strokeStyle = "#0000FF"; 18. ctx.stroke(); 19. // second part of the path 20. ctx.moveTo(120,20); 21. ctx.lineTo(200, 100); 22. ctx.lineTo(200,0); 23. // indicate stroke color + draw the path 24. ctx.fillStyle = "pink"; 25. ctx.fill(); 26. </script> 27. </body> 28. </html>
1. var canvas=document.getElementById('myCanvas'); 2. var ctx=canvas.getContext('2d'); 3. 4. // first part of the path 5. ctx.moveTo(20,20); 6. ctx.lineTo(100, 100); 7. ctx.lineTo(100,0); 8. 9. // indicate stroke color + draw first part of the path 10. ctx.strokeStyle = "#0000FF"; 11. ctx.stroke(); 12. 13. // second part of the path 14. ctx.moveTo(120,20); 15. ctx.lineTo(200, 100); 16. ctx.lineTo(200,0); 17. 18. // indicate stroke color + draw the path 19. ctx.fillStyle = "pink"; 20. ctx.fill();
Hey - it does not work! Weirdly, the two parts of the path are filled in pink! But we called stroke() after the first half of the path was drawn (lines 5-8). Then we called fill() only after the second part of the path was specified (lines 14-19)… so, what happened?
Remember that fill() or stroke() draws the whole path, even if it is disconnected, and even if it has already been drawn!
What happened is:
Important: If you do not want to draw parts of the same path several times, you need to draw two different paths, using the ctx.beginPath() method, as shown in the next example.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Drawing two paths with different styles: the right way!</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.getElementById('myCanvas'); 11. var ctx=canvas.getContext('2d'); 12. // first part of the path 13. ctx.moveTo(20,20); 14. ctx.lineTo(100, 100); 15. ctx.lineTo(100,0); 16. // indicate stroke color + draw first part of the path 17. ctx.strokeStyle = "#0000FF"; 18. ctx.stroke(); 19. // start a new path, empty the current buffer 20. ctx.beginPath(); 21. // second part of the path 22. ctx.moveTo(120,20); 23. ctx.lineTo(200, 100); 24. ctx.lineTo(200,0); 25. // indicate stroke color + draw the path 26. ctx.fillStyle = "pink"; 27. ctx.fill(); 28. </script> 29. </body> 30. </html>
1. var canvas=document.getElementById('myCanvas'); 2. var ctx=canvas.getContext('2d'); 3. 4. // first part of the path 5. ctx.moveTo(20,20); 6. ctx.lineTo(100, 100); 7. ctx.lineTo(100,0); 8. 9. // indicate stroke color + draw first part of the path 10. ctx.strokeStyle = "#0000FF"; 11. ctx.stroke(); 12. 13. // start a new path, empty the current buffer 14. ctx.beginPath(); 15. 16. // second part of the path 17. ctx.moveTo(120,20); 18. ctx.lineTo(200, 100); 19. ctx.lineTo(200,0); 20. 21. // indicate stroke color + draw the path 22. ctx.fillStyle = "pink"; 23. ctx.fill();
This time, in order to draw the two shapes differently, we defined two separate paths. The way to do this is just to call ctx.beginPath() to start a new path. In this example, the first path has been drawn in wireframe mode, then a new path has been started that is drawn in filled mode.
Sometimes, it might be useful to draw just one line.
It’s interesting to see how we can write a single “draw line” function that takes the start and end coordinates, the color, the line width, etc., and give the impression of being done in “immediate” mode.
1. function drawLine(x1, y1, x2, y2, color, width) { 2. ctx.save(); 3. 4. // set color and lineWidth, if these parameters 5. // are not defined, do nothing (default values) 6. if(color) 7. ctx.strokeStyle = color; 8. 9. if(width) 10. ctx.lineWidth = width; 11. 12. // start a new path 13. ctx.beginPath(); 14. 15. ctx.moveTo(x1, y1); 16. ctx.lineTo(x2, y2); 17. ctx.stroke(); 18. 19. ctx.restore(); 20. }
Notice the save/restore of the context at the beginning/end of the function. This is REALLY a best practice to avoid affecting other functions’ context.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Drawing lines in immediate mode</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.getElementById('myCanvas'); 11. var ctx=canvas.getContext('2d'); 12. drawLine(0, 0, 100, 100); 13. drawLine(0, 50, 150, 200, 'red'); 14. drawLine(10, 100, 100, 10, 'green', 10); 15. function drawLine(x1, y1, x2, y2, color, width) { 16. ctx.save(); 17. // set color and lineWidth, if these parameters 18. // are not defined, do nothing (default values) 19. if(color) 20. ctx.strokeStyle = color; 21. if(width) 22. ctx.lineWidth = width; 23. // start a new path 24. ctx.beginPath(); 25. ctx.moveTo(x1, y1); 26. ctx.lineTo(x2, y2); 27. ctx.stroke(); 28. ctx.restore(); 29. } 30. </script> 31. </body> 32. </html>
drawLine(0, 0, 100, 100); drawLine(0, 50, 150, 200, 'red'); drawLine(10, 100, 100, 10, 'green', 10);
In this section, we present a function that draws arrows in a canvas.
You may find multiple implementations on the Web for drawing arrows in a canvas, but the one we are presenting has the advantage of being rather simple and enables you to set the color and line width of the arrows.
1. // Adapted from : https://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag 2. function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ 3. //variables to be used when creating the arrow 4. var headlen = 10; 5. var angle = Math.atan2(toy-fromy,tox-fromx); 6. 7. ctx.save(); 8. ctx.strokeStyle = color; 9. 10. //starting path of the arrow from the start square to the end square 11. //and drawing the stroke 12. ctx.beginPath(); 13. ctx.moveTo(fromx, fromy); 14. ctx.lineTo(tox, toy); 15. ctx.lineWidth = arrowWidth; 16. ctx.stroke(); 17. 18. //starting a new path from the head of the arrow to one of the sides of 19. //the point 20. ctx.beginPath(); 21. ctx.moveTo(tox, toy); 22. ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7), 23. toy-headlen*Math.sin(angle-Math.PI/7)); 24. 25. //path from the side point of the arrow, to the other side point 26. ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7), 27. toy-headlen*Math.sin(angle+Math.PI/7)); 28. 29. //path from the side point back to the tip of the arrow, and then 30. //again to the opposite side point 31. ctx.lineTo(tox, toy); 32. ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7), 33. toy-headlen*Math.sin(angle-Math.PI/7)); 34. 35. //draws the paths created above 36. ctx.stroke(); 37. 38. ctx.restore(); 39. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Drawing arrows</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.getElementById('myCanvas'); 11. var ctx=canvas.getContext('2d'); 12. 13. // Adapted from : http://stackoverflow.com/questions/808826/draw-arrow-on-canvas-tag 14. function drawArrow(ctx, fromx, fromy, tox, toy, arrowWidth, color){ 15. //variables to be used when creating the arrow 16. var headlen = 10; 17. var angle = Math.atan2(toy-fromy,tox-fromx); 18. ctx.save(); 19. ctx.strokeStyle = color; 20. //starting path of the arrow from the start square to the end square 21. //and drawing the stroke 22. ctx.beginPath(); 23. ctx.moveTo(fromx, fromy); 24. ctx.lineTo(tox, toy); 25. ctx.lineWidth = arrowWidth; 26. ctx.stroke(); 27. //starting a new path from the head of the arrow to one of the sides of 28. //the point 29. ctx.beginPath(); 30. ctx.moveTo(tox, toy); 31. ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7), 32. toy-headlen*Math.sin(angle-Math.PI/7)); 33. //path from the side point of the arrow, to the other side point 34. ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7), 35. toy-headlen*Math.sin(angle+Math.PI/7)); 36. //path from the side point back to the tip of the arrow, and then 37. //again to the opposite side point 38. ctx.lineTo(tox, toy); 39. ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7), 40. toy-headlen*Math.sin(angle-Math.PI/7)); 41. //draws the paths created above 42. ctx.stroke(); 43. ctx.restore(); 44. } 45. drawArrow(ctx, 10, 10, 100, 100, 10, 'red'); 46. drawArrow(ctx, 100, 10, 140, 140, 3, 'black'); 47. </script> 48. </body> 49. </html>
drawArrow(ctx, 10, 10, 100, 100, 10, 'red'); drawArrow(ctx, 100, 10, 140, 140, 3, 'black');
On the Web, you will find many different ways to draw arrows.
This article on drawing lines and arcs with arrow heads is worth reading. It details how to draw arrows with curved heads and different styles for the head. Note, however, that you will need to modify some parts if you want it to support different line widths, etc.
Screenshot from a demo available on the above Web site:
In a later part of the course dedicated to curve drawing in a canvas, we will also show how to draw curved arrows, with very simple code (much simpler than the one used for drawing the clock’s hands above).
The ctx.closePath() method indicates that we would like a closed path: draw from the last point to the first.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Closing a path</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.getElementById('myCanvas'); 11. var ctx=canvas.getContext('2d'); 12. // Path made of three points (defines two lines) 13. ctx.moveTo(20,20); 14. ctx.lineTo(100, 100); 15. ctx.lineTo(100,0); 16. // Close the path, try commenting this line 17. ctx.closePath(); 18. // indicate stroke color + draw first part of the path 19. ctx.strokeStyle = "blue"; 20. ctx.stroke(); 21. </script> 22. </body> 23. </html>
1. var canvas=document.getElementById('myCanvas'); 2. var ctx=canvas.getContext('2d'); 3. 4. // Path made of three points (defines two lines) 5. ctx.moveTo(20,20); 6. ctx.lineTo(100, 100); 7. ctx.lineTo(100,0); 8. 9. // Close the path, try commenting this line 10. ctx.closePath(); 11. 12. // indicate stroke color + draw first part of the path 13. ctx.strokeStyle = "blue"; 14. ctx.stroke();
Try commenting the line 10 in the online example and see the results!
The ctx.arc(cx, cy, radius, startAngle, endAngle, drawInverse) method is useful for drawing arcs of circles. It takes the center of the circle/arc, its radius, the starting angle of the arc (turning clockwise), the ending angle of the arc, and an optional parameter we will talk about later.
Note: the figures in this page have been borrowed from the HTML5 Canvas Tutorials Web site.
1. ctx.arc(centerX, centerY, radius, startAngle, endAngle); // clockwise drawing 2. 3. ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);
The angles are in radians (between 0 and 2*Math.PI). The arc is drawn clockwise. Beware that this may not seem natural if you’re used to the trigonometric order.
The last parameter is optional and has a value of false by default. If true, instead of drawing an arc of circle that corresponds to the parameters, it will draw its complementary. See the examples below to see the difference.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Drawing an arc</title> 6. </head> 7. <body> 8. <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.getElementById('myCanvas'); 11. var ctx=canvas.getContext('2d'); 12. ctx.beginPath(); 13. // try to set the last parameter to true or remove it 14. ctx.arc(100,75,50,0,Math.PI/2); 15. ctx.lineWidth=10; 16. ctx.stroke(); 17. </script> 18. </body> 19. </html>
1. ctx.beginPath(); 2. // we ommited the last parameter 3. ctx.arc(100, 75, 50, 0, Math.PI/2); 4. 5. ctx.lineWidth = 10; 6. ctx.stroke();
And if we change the last parameter of the arc function call (line 3) to true (we omitted it, so it took a value of false by default) :
1. ctx.beginPath(); 2. // we omitted the last parameter 3. ctx.arc(100, 75, 50, 0, Math.PI/2, true); 4. 5. ctx.lineWidth = 10; 6. ctx.stroke();
Then, the result is the “complementary” of the previous arc:
1. var canvas = document.getElementById("myCanvas"); 2. var ctx = canvas.getContext("2d"); 3. var centerX = canvas.width / 2; 4. var centerY = canvas.height / 2; 5. var radius = 70; 6. 7. ctx.beginPath(); 8. 9. // Add to the path a full circle (from 0 to 2PI) 10. ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false); 11. 12. // With path drawing you can change the context 13. // properties until a call to stroke() or fill() is performed 14. ctx.fillStyle = "lightBlue"; 15. // Draws the filled circle in light blue 16. ctx.fill(); 17. 18. // Prepare for the outline 19. ctx.lineWidth = 5; 20. ctx.strokeStyle = "black"; 21. 22. // draws the path (the circle) AGAIN, this 23. // time in wireframe 24. ctx.stroke(); 25. 26. // Notice we called ctx.arc() only once ! And drew it twice 27. // with different styles
Notice that we called ctx.arc() only once! And drew it twice, with different styles, with calls to ctx.stroke() and ctx.fill(). Each call drew the defined path in wireframe and in filled mode!
There is another method called ctx.arcTo(x1, y1, x2, y2, radius), which is a bit complex to use, but very practical for drawing rounded rectangles.
In fact, the arcTo(…) method draws an arc of a circle depending on some tangents. Let’s look at these pictures for a better understanding:
1. ctx.moveTo(x0, y0); 2. 3. ctx.arcTo(x1, y1, x2, y2, radius);
This method can be confusing. It was defined mainly for drawing rounded shapes like rounded rectangles. We used an excerpt here from the excellent tutorial on the arcTo(…) method.
1. Draw an imaginary line through (x0,y0) and (x1,y1), draw another imaginary line through (x1,y1) and (x2,y2), 2. Take an imaginary circle of radius r, and slide it up between the two lines until it just touches both lines. The two points at which the circle touches the lines are called the tangent points. 3. arcTo(x1, y1, x2, y2, r) will draw a line from the current point (x0,y0) to the first tangent point on the line from (x0,y0) to (x1,y1), 4. It will also draw an arc from that tangent point to the other tangent point on the line from (x1,y1) to (x2,y2) along the circumference of the circle. 5. Finally, it adds the tangent point where the arc ends up, on the line from (x1,y1) to (x2,y2) to the path as the new current point on the path.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Simple use - arcTo - Example #1</title> 6. </head> 7. <body> 8. <canvas id="myCanvas" height = 400 width="800">Your browser does not 9. support the canvas tag.</canvas> 10. <script type="text/javascript"> 11. var canvas=document.getElementById('myCanvas'); 12. var context=canvas.getContext('2d'); 13. context.beginPath(); 14. context.moveTo(0, 20); 15. context.arcTo(100, 100, 200, 20, 50); 16. context.lineWidth = 5; 17. context.strokeStyle = "#0000ff"; 18. context.stroke(); 19. </script> 20. </body> 21. </html>
1. context.beginPath(); 2. context.moveTo(0, 20); 3. context.arcTo(100, 100, 200, 20, 50); 4. 5. context.lineWidth = 5; 6. context.strokeStyle = "#0000ff"; 7. context.stroke();
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Canvas</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var roundedRect=function(ctx,x,y,width,height,radius,fill,stroke) 11. { 12. ctx.beginPath(); 13. // draw top and top right corner 14. ctx.moveTo(x+radius,y); 15. ctx.arcTo(x+width,y,x+width,y+radius,radius); 16. // draw right side and bottom right corner 17. ctx.arcTo(x+width,y+height,x+width-radius,y+height,radius); 18. // draw bottom and bottom left corner 19. ctx.arcTo(x,y+height,x,y+height-radius,radius); 20. // draw left and top left corner 21. ctx.arcTo(x,y,x+radius,y,radius); 22. if(fill){ 23. ctx.fill(); 24. } 25. if(stroke){ 26. ctx.stroke(); 27. } 28. } 29. var canvas=document.getElementById('myCanvas'); 30. var ctx=canvas.getContext('2d'); 31. ctx.strokeStyle='rgb(150,0,0)'; 32. ctx.fillStyle='rgb(0,150,0)'; 33. ctx.lineWidth=7; 34. roundedRect(ctx,15,15,160,120,20,true,true); 35. </script> 36. </body>
1. var roundedRect=function(ctx,x,y,width,height,radius,fill,stroke) { 2. ctx.beginPath(); 3. 4. // draw top and top right corner 5. ctx.moveTo(x+radius,y); 6. ctx.arcTo(x+width,y,x+width,y+radius,radius); 7. // draw right side and bottom right corner 8. ctx.arcTo(x+width,y+height,x+width-radius,y+height,radius); 9. // draw bottom and bottom left corner 10. ctx.arcTo(x,y+height,x,y+height-radius,radius); 11. // draw left and top left corner 12. ctx.arcTo(x,y,x+radius,y,radius); 13. 14. if(fill) { 15. ctx.fill(); 16. } 17. 18. if(stroke){ 19. ctx.stroke(); 20. } 21. } 22. 23. var canvas = document.getElementById('myCanvas'); 24. var ctx = canvas.getContext('2d'); 25. 26. ctx.strokeStyle = 'rgb(150,0,0)'; 27. ctx.fillStyle = 'rgb(0,150,0)'; 28. ctx.lineWidth = 7; 29. 30. roundedRect(ctx, 15, 15, 160, 120, 20, true, true);
In this example, each call to ctx.arcTo(…) draws a side plus a corner. This makes us suspect that the arcTo() method has been designed primarily for drawing rounded rectangles…
This example at JS Bin is the same as the previous one, except that we added at the end of the roundedRect function the same lines of code that draw the rounded rectangle, but using lineTo instead of arcTo. Just take a look!
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Comparison between lineTo and arcTo - Example #3</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var roundedRect=function(ctx,x,y,width,height,radius,fill,stroke) 11. { 12. ctx.beginPath(); 13. // draw top and top right corner 14. ctx.moveTo(x+radius,y); 15. ctx.arcTo(x+width,y,x+width,y+radius,radius); 16. // draw right side and bottom right corner 17. ctx.arcTo(x+width,y+height,x+width-radius,y+height,radius); 18. // draw bottom and bottom left corner 19. ctx.arcTo(x,y+height,x,y+height-radius,radius); 20. // draw left and top left corner 21. ctx.arcTo(x,y,x+radius,y,radius); 22. if(fill){ 23. ctx.fill(); 24. } 25. if(stroke){ 26. ctx.stroke(); 27. } 28. // Draws the square using lineTo instead of arcTo, 29. // just to compare both methods 30. ctx.save(); 31. ctx.strokeStyle="pink"; 32. ctx.lineWidth=3; // ou ce que tu veux 33. ctx.beginPath(); 34. ctx.moveTo(x,y); 35. ctx.lineTo(x+width, y); 36. ctx.lineTo(x+width, y+height); 37. ctx.lineTo(x, y+height); 38. ctx.closePath(); 39. ctx.stroke(); 40. ctx.restore(); 41. } 42. var canvas=document.getElementById('myCanvas'); 43. var ctx=canvas.getContext('2d'); 44. ctx.strokeStyle='rgb(150,0,0)'; 45. ctx.fillStyle='rgb(0,150,0)'; 46. ctx.lineWidth=7; 47. roundedRect(ctx,15,15,160,120,20,true,true); 48. </script> 49. </body> 50. </html>
Red = arcTo and Pink = lineTo
For drawing a rounded square, this code also works:
1. ctx.moveTo(x+radius, y); 2. ctx.arcTo(x+width, y,x+width, y+height, radius); 3. ctx.arcTo(x+width, y+height, x, y+height, radius); 4. ctx.arcTo(x, y+height, x, y,radius); 5. ctx.arcTo(x, y, x+width, y,radius);
Which might be easier than trying to figure out where the arc will end like this:
1. ctx.moveTo(x+radius, y); 2. ctx.arcTo(x+width, y, x+width, y+radius, radius); 3. ctx.arcTo(x+width, y+height, x+width-radius, y+height,radius); 4. ctx.arcTo(x, y+height, x, y+height-radius, radius); 5. ctx.arcTo(x, y, x+radius, y,radius);
This could be particularly helpful if you are dealing with something other than a rectangle, like this rounded triangle:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Use the unrounded vertices in arcTo - Example #4</title> 6. </head> 7. <body> 8. <canvas id="myCanvas">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var roundedTriangle=function(ctx,x1,y1,x2,y2,x3,y3,radius,fill,stroke) 11. { 12. ctx.beginPath(); 13. // start at the middle of the side between x1,y1 and x2,y2 14. ctx.moveTo((x1+x2)/2,(y1+y2)/2); 15. // go around the x2,y2 vertex 16. ctx.arcTo(x2,y2,x3,y3,radius); 17. // go around the x3,y3 vertex 18. ctx.arcTo(x3,y3,x1,y1,radius); 19. // go around the x1,y1 vertex 20. ctx.arcTo(x1,y1,x2,y2,radius); 21. // and close the triangle with a line to the starting point 22. ctx.lineTo((x1+x2)/2,(y1+y2)/2); 23. if(fill){ 24. ctx.fill(); 25. } 26. if(stroke){ 27. ctx.stroke(); 28. } 29. } 30. var canvas=document.getElementById('myCanvas'); 31. var ctx=canvas.getContext('2d'); 32. ctx.strokeStyle='rgb(150,0,0)'; 33. ctx.fillStyle='rgb(0,150,0)'; 34. ctx.lineWidth=7; 35. roundedTriangle(ctx,200,15,300,150,15,100,20,true,true); 36. </script> 37. </body> 38. </html>
(Picture taken from the HTML5 Canvas Tutorials Web site)
Quadratic curves are defined by a starting point (called a “context point”), a control point, and an ending point. The curve fits the tangents between the context and control points and between the control and ending points.
The context point may be defined by a call to the moveTo(x, y) method of the context, or it may be the ending point of a previous path, if we’re drawing a path made of several shapes. For example, drawing a line and a quadratic curve will make the endpoint of the line the context point for the quadratic curve.
The control point controls the curvature - if we move the control point farther we get a sharper curve.
1. context.moveTo(contextX, contextY); 2. context.quadraticCurveTo(controlX, controlY, endX, endY); 3. // Optional : set lineWidth and stroke color 4. context.lineWidth = 5; 5. context.strokeStyle = "#0000ff"; 6. // Draw! 7. context.stroke();
1. var canvas=document.querySelector('#myCanvas1'); 2. var context=canvas.getContext('2d'); 3. 4. context.beginPath(); 5. 6. context.moveTo(100, 20); 7. context.quadraticCurveTo(230, 200, 250, 20); 8. 9. context.lineWidth = 5; 10. context.strokeStyle = "#0000ff"; 11. context.stroke();
We set a starting point in line 6: moveTo(…), then set the control and ending points with a call to quadraticCurve(…), at line 7, then set some properties for color, thickness, and finally we call the stroke() method for drawing the curve.
Try this:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"/> 5. <title>Lines connected with a quadratic curve - Example #2</title> 6. </head> 7. <body> 8. <canvas id="myCanvas1" height = 400 width="800">Your browser does not support the 9. canvas tag.</canvas> 10. <script type="text/javascript"> 11. var canvas=document.querySelector('#myCanvas1'); 12. var context=canvas.getContext('2d'); 13. context.beginPath(); 14. context.moveTo(100, 20); 15. context.lineTo(200, 80); 16. context.quadraticCurveTo(230, 200, 250, 20); 17. context.lineTo(500, 90); 18. context.lineWidth = 5; 19. context.strokeStyle = "#0000ff"; 20. context.stroke(); 21. </script> 22. </body> 23. </html>
1. context.beginPath(); 2. 3. context.moveTo(100, 20); 4. context.lineTo(200, 80); 5. context.quadraticCurveTo(230, 200, 250, 20); 6. context.lineTo(500, 90); 7. 8. context.lineWidth = 5; 9. context.strokeStyle = "#0000ff"; 10. context.stroke();
We propose a useful function for drawing curved arrows. See this example:
1. var canvas = document.querySelector('#myCanvas'); 2. var ctx = canvas.getContext('2d'); 3. var contextX = 100; 4. var contextY = 10; 5. var endPointX = 200; 6. var endPointY = 120; 7. var controlPointX = 35; 8. var controlPointY = 70; 9. drawCurvedArrow(contextX, contextY, 10. endPointX, endPointY, 11. controlPointX, controlPointY, 12. 3, // arrowWidth, try 30 for example ! 13. 20, // width of the arrow head, try smaller values, 10... 14. 'blue'); 15. function drawCurvedArrow(startPointX, startPointY, 16. endPointX, endPointY, 17. quadPointX, quadPointY, 18. lineWidth, 19. arrowWidth, 20. color) { 21. // GOOD PRACTICE: the function changes color and lineWidth -> save context! 22. ctx.save(); 23. ctx.strokeStyle = color; 24. ctx.lineWidth = lineWidth; 25. // angle of the end tangeant, useful for drawing the arrow head 26. var arrowAngle = Math.atan2(quadPointX - endPointX, quadPointY - endPointY) + Math.PI; 27. // start a new path 28. ctx.beginPath(); 29. ctx.moveTo(startPointX, startPointY); 30. ctx.quadraticCurveTo(quadPointX, quadPointY, endPointX, endPointY); 31. ctx.moveTo(endPointX - (arrowWidth * Math.sin(arrowAngle - Math.PI / 6)), 32. endPointY - (arrowWidth * Math.cos(arrowAngle - Math.PI / 6))); 33. ctx.lineTo(endPointX, endPointY); 34. ctx.lineTo(endPointX - (arrowWidth * Math.sin(arrowAngle + Math.PI / 6)), 35. endPointY - (arrowWidth * Math.cos(arrowAngle + Math.PI / 6))); 36. ctx.stroke(); 37. ctx.closePath(); 38. // GOOD PRACTICE -> restore the context as we saved it at the beginning 39. // of the function 40. ctx.restore(); 41. }
#myCanvas { border: 1px solid black; }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Curved arrows</title> 6. </head> 7. <body> 8. <canvas id="myCanvas" width="250" height="150"></canvas> 9. </body> 10. </html>
1. function drawCurvedArrow(startPointX, startPointY, 2. endPointX, endPointY, 3. quadPointX, quadPointY, 4. lineWidth, 5. arrowWidth, 6. color) { 7. // BEST PRACTICE: the function changes color and lineWidth -> save context! 8. ctx.save(); 9. 10. ctx.strokeStyle = color; 11. ctx.lineWidth = lineWidth; 12. 13. // angle of the end tangeant, useful for drawing the arrow head 14. var arrowAngle = Math.atan2(quadPointX - endPointX, quadPointY - endPointY) + Math.PI; 15. 16. // start a new path 17. ctx.beginPath(); 18. 19. // Body of the arrow 20. ctx.moveTo(startPointX, startPointY); 21. ctx.quadraticCurveTo(quadPointX, quadPointY, endPointX, endPointY); 22. 23. // Head of the arrow 24. ctx.moveTo(endPointX - (arrowWidth * Math.sin(arrowAngle - Math.PI / 6)), 25. endPointY - (arrowWidth * Math.cos(arrowAngle - Math.PI / 6))); 26. 27. ctx.lineTo(endPointX, endPointY); 28. 29. ctx.lineTo(endPointX - (arrowWidth * Math.sin(arrowAngle + Math.PI / 6)), 30. endPointY - (arrowWidth * Math.cos(arrowAngle + Math.PI / 6))); 31. 32. ctx.stroke(); 33. ctx.closePath(); 34. 35. // BEST PRACTICE -> restore the context as we saved it at the beginning 36. // of the function 37. ctx.restore(); 38. }
This function takes as parameters the start and end points, the control point of the curve, the arrow width, the width of the arrow head.
It computes the angle of the arrow at its endpoint (line 14) in order to compute the rotated endpoints of the two lines of the arrow head (lines 24 and 29).
Notice that once again, as we modify the context properties (color, lineWidth) in the body of the function, we save and restore the context at the beginning / end of the function.
Bézier curves are interesting. They are mostly used for drawing “S” shapes or asymmetric curves.
(image taken from SitePoint)
Bézier curves are defined by a context point, like quadratic curves, two control points that define two tangents, and an ending point.
The first part of the curve is tangential to the imaginary line defined by the context point and the first control point. The second part of the curve is tangential to the imaginary line defined by the second control point and the ending point.
(Picture taken from the HTML5 Canvas Tutorials Web site)
The best way to understand how they work is to check out one of these interactive applications:
1. ctx.moveTo(contextX, contextY); 2. context.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, endX, endY); 3. // Optional : set lineWidth and stroke color 4. context.lineWidth = 5; 5. context.strokeStyle = "#0000ff"; 6. // Draw! 7. ctx.stroke();
Try this:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Bézier curve - Example #1</title> 6. </head> 7. <body> 8. <canvas id="myCanvas1" height = 400 width="800">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.querySelector('#myCanvas1'); 11. var context=canvas.getContext('2d'); 12. context.beginPath(); 13. context.moveTo(100, 20); 14. // TRY uncommenting these lines ! 15. //context.lineTo(200, 80); 16. //context.quadraticCurveTo(230, 200, 250, 20); 17. context.bezierCurveTo(290, -40, 200, 200, 400, 100); 18. //context.lineTo(500, 90); 19. // TRY TO UNCOMMENT THIS LINE 20. //context.closePath(); 21. context.lineWidth = 5; 22. context.strokeStyle = "#0000ff"; 23. context.stroke(); 24. </script> 25. </body> 26. </html>
1. context.beginPath(); 2. 3. context.moveTo(100, 20); 4. context.bezierCurveTo(290, -40, 200, 200, 400, 100); 5. 6. context.lineWidth = 5; 7. context.strokeStyle = "#0000ff"; 8. context.stroke();
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Path with Bézier curve, quadratic curve and line in the same, closed path - Example #2</title> 6. </head> 7. <body> 8. <canvas id="myCanvas1 height = 400 width="800">Your browser does not support the canvas tag.</canvas> 9. <script type="text/javascript"> 10. var canvas=document.querySelector('#myCanvas1'); 11. var context=canvas.getContext('2d'); 12. context.beginPath(); 13. context.moveTo(100, 20); 14. context.lineTo(200, 160); 15. context.quadraticCurveTo(230, 200, 250, 120); 16. context.bezierCurveTo(290, -40, 300, 200, 400, 150); 17. context.lineTo(500, 90); 18. // TRY TO COMMENT THIS LINE 19. context.closePath(); 20. context.lineWidth = 5; 21. context.strokeStyle = "#0000ff"; 22. context.stroke(); 23. </script> 24. </body>
1. context.beginPath(); 2. 3. context.moveTo(100, 20); 4. 5. context.lineTo(200, 160); 6. context.quadraticCurveTo(230, 200, 250, 120); 7. context.bezierCurveTo(290, -40, 300, 200, 400, 150); 8. context.lineTo(500, 90); 9. 10. // TRY COMMENTING THIS LINE OUT 11. context.closePath(); 12. context.lineWidth = 5; 13. context.strokeStyle = "#0000ff"; 14. context.stroke();
In this example we use the closePath() method to draw a line between the last path point and the first path point (line 11), so that the drawing looks like a pair of goggles.
Note how the different parts are linked together and make a “path”:
This Bézier tool (“HTML5 <canvas> bezierCurveTo command generator”) is available online: try it!
In previous examples, we saw how to set the current color using the strokeStyle and fillStyle properties of the canvas context object.
Let’s look at color in a little more detail, and see how we can use gradients or patterns/textures/images (in other words: fill shapes or fill the outline of the shapes with some images that repeat themselves).
You can use the same syntax for colors that is supported by CSS3. The next lines show possible values/syntaxes.
1. ctx.strokeStyle = 'red'; 2. ctx.fillStyle = "#00ff00"; 3. ctx.strokeStyle = "rgb(0, 0, 255)"; 4. ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
Note that:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Colors and transparency</title> 6. <style> 7. #myCanvas { 8. border: 1px solid black; 9. } 10. </style> 11. <script> 12. var canvas, ctx; 13. function init() { 14. // This function is called after the page is loaded 15. // 1 - Get the canvas 16. canvas = document.getElementById('myCanvas'); 17. // 2 - Get the context 18. ctx=canvas.getContext('2d'); 19. // 3 - we can draw 20. drawSomething(); 21. } 22. function drawSomething() { 23. // set the global context values 24. ctx.fillStyle='rgba(0, 0, 255, 0.2)'; 25. // Draw the two filled red rectangles 26. ctx.fillRect(150, 20, 200, 100); 27. ctx.fillRect(100, 50, 200, 100); 28. ctx.fillStyle = "blue"; 29. ctx.fillRect(50, 100, 200, 100); 30. } 31. </script> 32. </head> 33. <body onload="init();"> 34. <canvas id="myCanvas" width="400" height="220"> 35. Your browser does not support the canvas tag. 36. </canvas> 37. </body> 38. </html>
It is possible to define the stroke or the fill style as a “gradient”, a set of interpolated colors, like in this example below:
1. var canvas, ctx, grdFrenchFlag; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. // The gradient we create is also a global variable, we 7. // will be able to reuse it for drawing different shapes 8. // in different functions 9. grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 0); 10. // Try adding colors with first parameter between 0 and 1 11. grdFrenchFlag.addColorStop(0, "blue"); 12. grdFrenchFlag.addColorStop(0.5, "white"); 13. grdFrenchFlag.addColorStop(1, "red"); 14. draw(); 15. } 16. 17. function draw() { 18. ctx.fillStyle = grdFrenchFlag; 19. ctx.fillRect(0, 0, 300, 200); 20. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Linear gradients</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
The gradient becomes visible when we draw shapes on top of the invisible gradient, and when the fillStyle or strokeStyle property has for value this gradient.
There are 3 steps:
ctx.createLinearGradient(x0,y0,x1,y1);
… where the (x0, y0) and (x1, y1) parameters define “the direction of the gradient” (as a vector with a starting and an ending point). This direction is an invisible line along which the colors that compose the gradient will be interpolated.
grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 0);
This line defines the direction of the gradient: a virtual, invisible line that goes from the top left corner of the canvas (0, 0) to the top right corner of the canvas (300, 0). The interpolated colors will propagate along this line.
If this gradient is going to be reused by different functions, it is good practice to create/initialize it in a function called when the page is loaded and to store it in a global variable.
We will add a set of “colors” and “stops” to this gradient. The stops go from 0 (beginning of the virtual line defined just above), to 1 (end of the virtual line). A color associated with a value of 0.5 will be right in the middle of the virtual line.
Here is an example that corresponds to an interpolated version of the French flag, going from blue to white, then to red, with proportional intervals. We define three colors, blue at position 0, white at position 0.5 and red at position 1:
grdFrenchFlag.addColorStop(0, "blue"); grdFrenchFlag.addColorStop(0.5, "white"); grdFrenchFlag.addColorStop(1, "red");
First, let’s set the fillStyle or strokeStyle of the context with this gradient, then let’s draw some shapes “on top of the gradient”.
In our example, the gradient corresponds to an invisible rectangle that fills the canvas. If we draw a rectangle of the canvas size, it should be filled with the entire gradient:
ctx.fillStyle = grdFrenchFlag; ctx.fillRect(0, 0, 300, 200);
The result is shown in the above pen: a big rectangle that fills the whole canvas, with colors going from blue (left) to white (middle) to red (right).
If you modify the source code that defines the direction of the gradient as follows…
grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 200);
… then you will define a gradient that goes from the top left corner of the canvas to the bottom right of the canvas. Let’s see what it does:
Instead of drawing a filled rectangle that covers the whole surface of the canvas, let’s draw several smaller rectangles:
Note that the canvas has its default background color where we did not draw anything. And where we have drawn rectangles, we can see “through” and the colors from the gradient are visible.
1. ctx.fillStyle = grdFrenchFlag; 2. ctx.fillRect(0, 0, 50, 50); 3. ctx.fillRect(100, 0, 50, 50); 4. ctx.fillRect(200, 0, 50, 50); 5. ctx.fillRect(50, 50, 50, 50); 6. ctx.fillRect(150, 50, 50, 50); 7. ctx.fillRect(250, 50, 50, 50); 8. ctx.fillRect(0, 100, 50, 50); 9. ctx.fillRect(100, 100, 50, 50); 10. ctx.fillRect(200, 100, 50, 50); 11. ctx.fillRect(50, 150, 50, 50); 12. ctx.fillRect(150, 150, 50, 50); 13. ctx.fillRect(250, 150, 50, 50);
This code is rather ugly isn’t it? It would have been better to use a loop…
1. // n = number of cells per row/column 2. function drawCheckboard(n) { 3. ctx.fillStyle = grdFrenchFlag; 4. 5. var l = canvas.width; 6. var h = canvas.height; 7. 8. var cellWidth = l / n; 9. var cellHeight = h / n; 10. 11. for(i = 0; i < n; i++) { 12. for(j = i % 2; j < n; j++) { 13. ctx.fillRect(cellWidth*i, cellHeight*j, cellWidth, cellHeight); 14. } 15. } 16. }
The two loops (lines 11-15) draw only one cell out of two (see the j = i % 2 at line 12). i is the column number and if the column is odd or even, either we draw or we do not draw a rectangle.
This code is much more complex than the previous one, taking 16 lines instead of 13, but is much more powerful. Try to call the function with a value of 10, 20, or 2…
1. var canvas, ctx, grdFrenchFlag; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. // The gradient we create is also a global variable, we 7. // will be able to reuse it for drawing different shapes 8. // in different functions 9. grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 200); 10. // Try adding colors with first parameter between 0 and 1 11. grdFrenchFlag.addColorStop(0, "blue"); 12. grdFrenchFlag.addColorStop(0.5, "white"); 13. grdFrenchFlag.addColorStop(1, "red"); 14. drawCheckboard(10); 15. } 16. 17. // n = number of cells per row/column 18. function drawCheckboard(n) { 19. ctx.fillStyle = grdFrenchFlag; 20. ctx.lineWidth=10; 21. var l = canvas.width; 22. var h = canvas.height; 23. var cellWidth = l / n; 24. var cellHeight = h / n; 25. for(i = 0; i < n; i++) { 26. for(j = i % 2; j < n; j+=2) { 27. ctx.fillRect(cellWidth*i, cellHeight*j, cellWidth, cellHeight); 28. } 29. } 30. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Drawing shapes that do not cover the whole gradient - Example #2 bis</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag. 14. </canvas> 15. </body> 16. </html>
Just as we used fillStyle and fillRect for drawing rectangles filled with a gradient, we can also use strokeStyle and strokeRect in order to draw wireframed rectangles. In the next example, which is just a variation of the previous one, we have used the lineWidth property to set the outline of the rectangles at 5 pixels:
1. var canvas, ctx, grdFrenchFlag; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. // The gradient we create is also a global variable, we 7. // will be able to reuse it for drawing different shapes 8. // in different functions 9. grdFrenchFlag = ctx.createLinearGradient(0, 0, 300, 200); 10. // Try adding colors with first parameter between 0 and 1 11. grdFrenchFlag.addColorStop(0, "blue"); 12. grdFrenchFlag.addColorStop(0.5, "white"); 13. grdFrenchFlag.addColorStop(1, "red"); 14. drawCheckboard(5); 15. } 16. 17. // n = number of cells per row/column 18. function drawCheckboard(n) { 19. ctx.strokeStyle = grdFrenchFlag; 20. ctx.lineWidth=10; 21. var l = canvas.width; 22. var h = canvas.height; 23. var cellWidth = l / n; 24. var cellHeight = h / n; 25. for(i = 0; i < n; i++) { 26. for(j = i % 2; j < n; j+=2) { 27. ctx.strokeRect(cellWidth*(i), cellHeight*j, cellWidth, cellHeight); 28. } 29. } 30. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Drawing outlined shapes with gradients - Example #3</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
1. function drawCheckboard(n) { 2. ctx.strokeStyle = grdFrenchFlag; 3. ctx.lineWidth=10; 4. ... 5. for(i = 0; i < n; i++) { 6. for(j = i % 2; j < n; j++) { 7. ctx.strokeRect(cellWidth*i, cellHeight*j, cellWidth, cellHeight); 8. } 9. } 10. }
Let’s go back to the very first example on this page - the one with the blue-white-red interpolated French flag. This time we will define a smaller gradient. Instead of going from (0, 0) to (300, 0), it will go from (100, 0) to (200, 0), while the canvas remains the same (width=300, height=200).
grdFrenchFlag = ctx.createLinearGradient(100, 0, 200, 0);
Like in the first example we will draw a filled rectangle that is the same size as the canvas:
1. var canvas, ctx, grdFrenchFlag; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. // The gradient we create is also a global variable, we 7. // will be able to reuse it for drawing different shapes 8. // in different functions 9. grdFrenchFlag = ctx.createLinearGradient(100, 0, 200, 0); 10. // Try adding colors with first parameter between 0 and 1 11. grdFrenchFlag.addColorStop(0, "blue"); 12. grdFrenchFlag.addColorStop(0.5, "white"); 13. grdFrenchFlag.addColorStop(1, "red"); 14. draw(); 15. } 16. 17. function draw() { 18. ctx.fillStyle = grdFrenchFlag; 19. ctx.fillRect(0, 0, 300, 200); 20. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Defining a gradient smaller than the canvas - Example #4</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
We notice that “before” the gradient starts, the first color of the gradient is repeated without any interpolation (columns 0-100 are all blue), then we “see through” and the gradient is drawn (columns 100-200), then the last color of the gradient is repeated without any interpolation (columns 200-300 are red).
Nothing special; we will “see through the drawn shapes”, and the parts of the gradient that are located in the canvas area will be shown. You can try this example that defines a gradient twice the size of the canvas:
grdFrenchFlag = ctx.createLinearGradient(0, 0, 600, 400);
And if we draw the same rectangle with the canvas size, here is the result:
1. var canvas, ctx, grdFrenchFlag; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. // The gradient we create is also a global variable, we 7. // will be able to reuse it for drawing different shapes 8. // in different functions 9. grdFrenchFlag = ctx.createLinearGradient(0, 0, 600, 400); 10. // Try adding colors with first parameter between 0 and 1 11. grdFrenchFlag.addColorStop(0, "blue"); 12. grdFrenchFlag.addColorStop(0.5, "white"); 13. grdFrenchFlag.addColorStop(1, "red"); 14. draw(); 15. 16. } 17. function draw() { 18. ctx.fillStyle = grdFrenchFlag; 19. ctx.fillRect(0, 0, 300, 200); 20. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Defining a gradient bigger than the canvas - Example #5</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
The red color is beyond the bottom right corner…. we see only the top left quarter of the gradient.
This time, we would like to draw the chessboard with the gradient in each cell. How can we do this with one single gradient?
We can’t! At least we can’t without recreating it for each cell!
It suffices to create a new gradient before drawing each filled rectangle, and set it with the starting and ending point of its direction/virtual line accordingly to the rectangle coordinates. Try this:
1. var canvas, ctx, grdFrenchFlag; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector(#myCanvas1); 5. ctx = canvas.getContext('2d'); 6. drawCheckboard(5); 7. } 8. 9. function setGradient(x, y, width, height) { 10. grdFrenchFlag = ctx.createLinearGradient(x, y, width, height); 11. grdFrenchFlag.addColorStop(0, "blue"); 12. grdFrenchFlag.addColorStop(0.5, "white"); 13. grdFrenchFlag.addColorStop(1, "red"); 14. ctx.fillStyle = grdFrenchFlag; 15. } 16. 17. // n = number of cells per row/column 18. function drawCheckboard(n) { 19. var l = canvas.width; 20. var h = canvas.height; 21. var cellWidth = l / n; 22. var cellHeight = h / n; 23. for(i = 0; i < n; i+=2) { 24. for(j = 0; j < n; j++) { 25. var x = cellWidth*(i+j%2); 26. var y = cellHeight*j; 27. setGradient(x, y, x+cellWidth, y+cellHeight); 28. ctx.fillRect(x, y, cellWidth, cellHeight);
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Drawing shapes that share the same gradient as a whole - Example #6</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
1. function setGradient(x, y, width, height) { 2. grdFrenchFlag = ctx.createLinearGradient(x, y, width, height); 3. grdFrenchFlag.addColorStop(0, "blue"); 4. grdFrenchFlag.addColorStop(0.5, "white"); 5. grdFrenchFlag.addColorStop(1, "red"); 6. // set the new gradient to the current fillStyle 7. ctx.fillStyle = grdFrenchFlag; 8. } 9. 10. // n = number of cells per row/column 11. function drawCheckboard(n) { 12. var l = canvas.width; 13. var h = canvas.height; 14. var cellWidth = l / n; 15. var cellHeight = h / n; 16. for(i = 0; i < n; i+=2) { 17. for(j = 0; j < n; j++) { 18. var x = cellWidth*(i+j%2); 19. var y = cellHeight*j; 20. setGradient(x, y, x+cellWidth, y+cellHeight); 21. ctx.fillRect(x, y, cellWidth, cellHeight); 22. } 23. } 24. }
We wrote a function setGradient(startX, startY, endX, endY) that creates a gradient and set the fillStyle context property so that any filled shape drawn will have this gradient.
In the drawCheckBoard(…) function we call it just before drawing rectangles. In this way, each rectangle is drawn using its own gradient.
Radial gradients are for creating gradients that propagate/interpolate colors along circles instead of propagating/interpolating along a virtual line, like linear gradients.
1. var canvas, ctx, grd; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. grd = ctx.createRadialGradient(150, 100, 30, 150, 100, 100); 7. grd.addColorStop(0, "red"); 8. grd.addColorStop(0.17, "orange"); 9. grd.addColorStop(0.33, "yellow"); 10. grd.addColorStop(0.5, "green"); 11. grd.addColorStop(0.666, "blue"); 12. grd.addColorStop(1, "violet"); 13. draw(); 14. } 15. function draw() { 16. ctx.fillStyle = grd; 17. ctx.fillRect(0, 0, 300, 200); 18. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Radial gradients</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
1. var grd = context.createRadialGradient(150, 100, 30, 150, 100, 100); 2. grd.addColorStop(0, "red"); 3. grd.addColorStop(0.17, "orange"); 4. grd.addColorStop(0.33, "yellow"); 5. grd.addColorStop(0.5, "green"); 6. grd.addColorStop(0.666, "blue"); 7. grd.addColorStop(1, "violet"); 8. 9. context.fillStyle = grd;
The method from the context object createRadialGradient(cx1, cy1, radius1, cx2, cy2, radius2) takes as the first three parameters the “starting” circle of the gradient, and as the three last parameters, the “ending circle”.
In the above example, the gradients starts at a circle located at (150, 100), with a radius of 30, and propagates to a circle with the same center as the first (150, 100), but with a bigger radius of 100, as shown below:We added color stops using a method similar to that used for linear gradients.
You get some nice effects; here we set the second circle’s center 60 pixels to the right of the first circle’s center (cx = 210 instead of 150):
grd = ctx.createRadialGradient(150, 100, 30, 210, 100, 100);
1. var canvas, ctx, grd; 2. function init() { 3. // Good practice 1: set global vars canvas, ctx, gradients, etc here 4. canvas = document.querySelector('#myCanvas1'); 5. ctx = canvas.getContext('2d'); 6. grd = ctx.createRadialGradient(150, 100, 30, 210, 100, 100); 7. grd.addColorStop(0, "red"); 8. grd.addColorStop(0.17, "orange"); 9. grd.addColorStop(0.33, "yellow"); 10. grd.addColorStop(0.5, "green"); 11. grd.addColorStop(0.666, "blue"); 12. grd.addColorStop(1, "violet"); 13. draw(); 14. } 15. 16. function draw() { 17. ctx.fillStyle = grd; 18. ctx.fillRect(0, 0, 300, 200); 19. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Radial gradients with offset circles</title> 6. <style> 7. #myCanvas1 { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas1" width="300" height=200>Your browser does not support the canvas tag.</canvas> 14. </body> 15. </html>
A gradient is an invisible shape on the screen: the radial gradient is made of two circles: an inner and an outer circle. Between these two circles, colors are interpolated.
We call the “first color” the color defined for the inner circle, the “last color” the last color of the gradient, that corresponds to the outer circle:
The principle of “pattern” drawing is based on repeating an image (if the image is smaller than the surface of the shape you are going to draw) for filling the surface of objects to be drawn (either filled or stroked).
To illustrate this principle, in the next examples, we are going to draw rectangles using a pattern.
There are a few steps we have to take before doing this:
var imageObj = new Image();
imageObj.onload = function(){ ... }
imageObj.src = "https://www.myserver.com/myRepeatablePattern.png";
As soon as step 3 is executed, an HTTP request is sent in background by the browser, and when the image is loaded in memory, the callback defined at step 2 is called. We create a pattern object inside, from the loaded image:
// callback called asynchronously, after the src attribute of imageObj is set imageObj.onload = function(){ // We enter here when the image is loaded, we create a pattern object. // It is good practice to set this as a global variable, easier to share pattern1 = ctx.createPattern(imageObj, "repeat"); };
1. // callback called asynchronously, after the src attribute of imageObj is set 2. imageObj.onload = function(){ 3. pattern1 = ctx.createPattern(imageObj, "repeat"); 4. 5. // Draw a textured rectangle 6. ctx.fillStyle = pattern1; 7. ctx.fillRect(10, 10, 500, 800); 8. };
Here we have two rectangles drawn using a pattern (an image that can be repeated along the X and Y axis). The first is a filled rectangle while the second is “stroked” with a lineWidth of 20 pixels:
1. var canvas, ctx, pattern1; 2. function init() { 3. canvas = document.querySelector('#myCanvas'); 4. ctx = canvas.getContext('2d'); 5. // We need 1) to create an empty image object, 2) to set a callback function 6. // that will be called when the image is fully loaded, 3) to create a 7. // pattern object, 4) to set the fillStyle or the strokeStyle property of 8. // the context with this pattern, 5) to draw something 9. // WE CANNOT DRAW UNTIL THE IMAGE IS FULLY LOADED -> draw from inside the 10. // onload callback only ! 11. // Allocate an image 12. var imageObj = new Image(); 13. // callback called asynchronously, after the src attribute of imageObj is set 14. imageObj.onload = function(){ 15. // We enter here only when the image has been loaded by the browser 16. // Pattern creation using the image object 17. // Instead of "repeat", try different values : repeat-x, repeat-y, 18. // or no-repeat, You may draw larger shapes in order to see 19. // different results 20. // It is a good practice to leave this as a global variable if it 21. // will be reused by other functions 22. pattern1 = ctx.createPattern(imageObj, "repeat"); 23. // Draw a textured rectangle 24. ctx.fillStyle = pattern1; 25. ctx.fillRect(10, 10, 200, 200); 26. // And a wireframe one 27. ctx.lineWidth=20; 28. ctx.strokeStyle=pattern1; 29. ctx.strokeRect(230, 20, 150, 100); 30. }; 31. // This will tell the browser to send an asynchronous request. 32. // When the browser will get an answer, the callback above will be called 33. imageObj.src = "https://mainline.i3s.unice.fr/mooc/pattern1.jpg"; 34. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Drawing two rectangles with a pattern - Example #1</title> 6. </head> 7. <body onload="init();"> 8. <canvas id="myCanvas" width="500" height="400"> 9. Your browser does not support the canvas tag. </canvas> 10. </body> 11. </html>
1. var canvas, ctx, pattern1; 2. 3. function init() { 4. canvas = document.querySelector('#myCanvas'); 5. ctx = canvas.getContext('2d'); 6. 7. // We need 1) to create an empty image object, 2) to set a callback function 8. // that will be called when the image is fully loaded, 3) to create a 9. // pattern object, 4) to set the fillStyle or the strokeStyle property of 10. // the context with this pattern, 5) to draw something 11. // WE CANNOT DRAW UNTIL THE IMAGE IS FULLY LOADED -> draw from inside the 12. // onload callback only ! 13. 14. 15. // 1 - Allocate an image 16. var imageObj = new Image(); 17. 18. // 2 - callback called asynchronously, after the src attribute of imageObj 19. // is set 20. imageObj.onload = function(){ 21. // We enter here only when the image has been loaded by the browser 22. // 4 - Pattern creation using the image object 23. // Instead of "repeat", try different values : repeat-x, repeat-y, 24. // or no-repeat, You may draw larger shapes in order to see 25. // different results 26. // It is good practice to leave this as a global variable if it 27. // will be reused by other functions 28. 29. pattern1 = ctx.createPattern(imageObj, "repeat"); 30. 31. // 5 - Draw things. Here a textured rectangle 32. ctx.fillStyle = pattern1; 33. ctx.fillRect(10, 10, 200, 200); 34. 35. // ... And a wireframe one 36. ctx.lineWidth=20; 37. ctx.strokeStyle=pattern1; 38. ctx.strokeRect(230, 20, 150, 100); 39. }; 40. 41. // 3 - Send the request to load the image 42. // Setting the src attribute will tell the browser to send an asynchronous 43. // request. 44. // When the browser gets an answer, the callback above will be called 45. imageObj.src = "https://mainline.i3s.unice.fr/mooc/pattern1.jpg"; 46. }
To “better” see the repeatability of the pattern, here is the same example with a 1000x1000 pixel wide canvas:
1. var canvas, ctx, pattern1; 2. function init() { 3. canvas = document.querySelector('#myCanvas'); 4. ctx = canvas.getContext('2d'); 5. // We need 1) to create an empty image object, 2) to set a callback function 6. // that will be called when the image is fully loaded, 3) to create a 7. // pattern object, 4) to set the fillStyle or the strokeStyle property of 8. // the context with this pattern, 5) to draw something 9. // WE CANNOT DRAW UNTIL THE IMAGE IS FULLY LOADED -> draw from inside the 10. // onload callback only ! 11. // Allocate an image 12. var imageObj = new Image(); 13. // callback called asynchronously, after the src attribute of imageObj is set 14. imageObj.onload = function(){ 15. // We enter here only when the image has been loaded by the browser 16. // Pattern creation using the image object 17. // Instead of "repeat", try different values : repeat-x, repeat-y, 18. // or no-repeat, You may draw larger shapes in order to see 19. // different results 20. // It is a good practice to leave this as a global variable if it 21. // will be reused by other functions 22. pattern1 = ctx.createPattern(imageObj, "repeat"); 23. // Draw a textured rectangle 24. ctx.fillStyle = pattern1; 25. ctx.fillRect(10, 10, 500, 800); 26. // And a wireframe one 27. ctx.lineWidth=50; 28. ctx.strokeStyle=pattern1; 29. ctx.strokeRect(650, 20, 300, 800); 30. }; 31. // This will tell the browser to send an asynchronous request. 32. // When the browser will get an answer, the callback above will be called 33. imageObj.src = "https://mainline.i3s.unice.fr/mooc/pattern1.jpg"; 34. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Repeatability of a pattern - Example #2</title> 6. </head> 7. <body onload="init();"> 8. <canvas id="myCanvas" width="1000" height="1000"> 9. Your browser does not support the canvas tag. </canvas> 10. </body> 11. </html>
1. pattern1 = ctx.createPattern(imageObj, "repeat");
Please try: repeat-x, repeat-y or no-repeat as acceptable values. Just change this line in the online example and you will see live results.
Below are 4 rectangles drawn with 4 different patterns.
We said earlier that we cannot draw before the image used by a pattern is loaded. This can become rapidly complicated if we need to draw using multiple patterns. We need a way to load all images and then, only when all images have been loaded, start drawing.
JavaScript is an asynchronous language. When you set the src attribute of an image, then an asynchronous request is sent by the browser, and then after a while, the onload callback is called… The difficult part to understand for those who are not familiar with JavaScript is that these requests are done in parallel and we do not know when, and in what order, the images will be loaded.
The solution is to use a multiple image loader that counts the loaded images and calls a function you pass when done!
The trick is to have an array of URLs that will be used by our multiple image loader, and have the onload callback called once per image loaded, so we can count the number of images effectively loaded.
When all images have been loaded, we call a callback function that has been passed to our loader.
The complete example code that produces the result shown at the beginning of this page is the following:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>A multiple image loader</title> 6. <style> 7. #myCanvas { 8. border: 1px solid black; 9. } 10. </style> 11. <script> 12. var canvas, ctx; 13. // List of images to load, we used a JavaScript object instead of 14. // an array, so that named indexes (aka properties) 15. // can be used -> easier to manipulate 16. var imagesToLoad = { 17. flowers: https://i.ibb.co/4NN9Sgn/flowers.jpg, 18. lion: https://i.ibb.co/3NyqKnY/lion.jpg, 19. blackAndWhiteLys: https://i.ibb.co/VNLVpcL/final.jpg, 20. tiledFloor: 21. https://i.ibb.co/Dt6txmG/repeatable-Pattern.jpg{ 22. }; 23. 24. function loadImages(imagesToBeLoaded, drawCallback) { 25. var imagesLoaded = {}; 26. var loadedImages = 0; 27. var numberOfImagesToLoad = 0; 28. // get num of sources 29. for(var name in imagesToBeLoaded) { 30. numberOfImagesToLoad++; 31. } 32. for(var name in imagesToBeLoaded) { 33. imagesLoaded[name] = new Image(); 34. imagesLoaded[name].onload = function() { 35. if(++loadedImages >= numberOfImagesToLoad) { 36. drawCallback(imagesLoaded); 37. } // if 38. }; // function 39. imagesLoaded[name].src = imagesToBeLoaded[name]; 40. } // for 41. } // function 42. 43. function init() { 44. // This function is called after the page is loaded 45. // 1 - Get the canvas 46. canvas = document.getElementById('myCanvas'); 47. // 2 - Get the context 48. ctx=canvas.getContext('2d'); 49. loadImages(imagesToLoad, function(imagesLoaded) { 50. patternFlowers = ctx.createPattern(imagesLoaded.flowers, repeat); 51. patternLion = ctx.createPattern(imagesLoaded.lion, repeat); 52. patternBW = ctx.createPattern(imagesLoaded.blackAndWhiteLys, repeat); 53. patternFloor = ctx.createPattern(imagesLoaded.tiledFloor, repeat); 54. drawRectanglesWithPatterns(); 55. }); 56. } 57. 58. function drawRectanglesWithPatterns() { 59. ctx.fillStyle=patternFloor; 60. ctx.fillRect(0,0,200,200); 61. ctx.fillStyle=patternLion; 62. ctx.fillRect(200,0,200,200); 63. ctx.fillStyle=patternFlowers; 64. ctx.fillRect(0,200,200,200); 65. ctx.fillStyle=patternBW; 66. ctx.fillRect(200,200,200,200); 67. } 68. </script> 69. </head> 70. <body onload="init();"> 71. <canvas id="myCanvas" width="400" height="400"> 72. Your browser does not support the canvas tag. 73. </canvas> 74. </body>
1. // List of images to load, we used a JavaScript object instead of 2. // an array, so that named indexes (aka properties) 3. // can be used -> easier to read 4. var imagesToLoad = { 5. flowers: https://i.ibb.co/4NN9Sgn/flowers.jpg, 6. lion: https://i.ibb.co/3NyqKnY/lion.jpg, 7. blackAndWhiteLys: https://i.ibb.co/VNLVpcL/final.jpg, 8. tiledFloor: 9. https://i.ibb.co/Dt6txmG/repeatable-Pattern.jpg 10. };
Notice that instead of using a traditional array, we defined this list as a JavaScript object, with properties whose names will be easier to manipulate (flowers, lion, tiledFloor, etc.).
1. function loadImages(imagesToBeLoaded, drawCallback) { 2. var imagesLoaded = {}; 3. var loadedImages = 0; 4. var numberOfImagesToLoad = 0; 5. 6. // get num of images to load 7. for(var name in imagesToBeLoaded) { 8. numberOfImagesToLoad++; 9. } 10. 11. for(var name in imagesToBeLoaded) { 12. imagesLoaded[name] = new Image(); 13. 14. imagesLoaded[name].onload = function() { 15. if(++loadedImages >= numberOfImagesToLoad) { 16. drawCallback(imagesLoaded); 17. } // if 18. }; // function 19. imagesLoaded[name].src = imagesToBeLoaded[name]; 20. } // for 21. } // function
1. loadImages(imagesToLoad, function(imagesLoaded) { 2. patternFlowers = ctx.createPattern(imagesLoaded.flowers, 'repeat'); 3. patternLion = ctx.createPattern(imagesLoaded.lion, 'repeat'); 4. patternBW = ctx.createPattern(imagesLoaded.blackAndWhiteLys, 'repeat'); 5. patternFloor = ctx.createPattern(imagesLoaded.tiledFloor, 'repeat'); 6. 7. drawRectanglesWithPatterns(); 8. });
1. function drawRectanglesWithPatterns() { 2. ctx.fillStyle=patternFloor; 3. ctx.fillRect(0,0,200,200); 4. 5. ctx.fillStyle=patternLion; 6. ctx.fillRect(200,0,200,200); 7. 8. ctx.fillStyle=patternFlowers; 9. ctx.fillRect(0,200,200,200); 10. 11. ctx.fillStyle=patternBW; 12. ctx.fillRect(200,200,200,200); 13. }
There are 4 properties of the canvas context that are useful for indicating that we want to draw shapes with shadows:
1. var canvas, ctx; 2. function init() { 3. canvas = document.getElementById('myCanvas'); 4. ctx = canvas.getContext('2d'); 5. setShadow(); 6. // first green filled rectangle 7. ctx.fillStyle = "#22FFDD"; // rectangle color 8. ctx.fillRect(20, 20, 200, 100); 9. // second stroked rectangle 10. ctx.strokeStyle = "purple"; // rectangle color 11. ctx.lineWidth=10; 12. ctx.strokeRect(20, 150, 200, 100); 13. } 14. function setShadow() { 15. ctx.shadowColor = "Grey"; // color 16. ctx.shadowBlur = 20; // blur level 17. ctx.shadowOffsetX = 15; // horizontal offset 18. ctx.shadowOffsetY = 15; // vertical offset 19. }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Simple shadows - Example #1</title> </head> <body onload = init();> <canvas id="myCanvas" width="400" height =800> Your browser does not support the canvas tag.</canvas> </body> </html>
1. var canvas, ctx; 2. 3. function init() { 4. canvas = document.getElementById('myCanvas'); 5. ctx = canvas.getContext('2d'); 6. 7. // call to a function that will set the 4 context properties for shadows 8. setShadow(); 9. // all drawings that will occur will cast shadows 10. 11. // first green filled rectangle 12. ctx.fillStyle = "#22FFDD"; 13. ctx.fillRect(20, 20, 200, 100); 14. 15. // second stroked rectangle 16. ctx.strokeStyle = "purple"; 17. ctx.lineWidth=10; 18. ctx.strokeRect(20, 150, 200, 100); 19. } 20. 21. // We define the 4 properties in a dedicated function, for clarity 22. function setShadow() { 23. ctx.shadowColor = "Grey"; // color 24. ctx.shadowBlur = 20; // blur level 25. ctx.shadowOffsetX = 15; // horizontal offset 26. ctx.shadowOffsetY = 15; // vertical offset 27. }
Let’s take a previous example, the one that draws a filled circle with an outline. And, let’s add a shadow to it using the following code:
1. ... 2. ctx.beginPath(); 3. 4. // Add to the path a full circle (from 0 to 2PI) 5. ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false); 6. 7. // With path drawing you can change the context 8. // properties until a call to stroke() or fill() is performed 9. ctx.fillStyle = "lightBlue"; 10. 11. // add shadows before drawing the filled circle 12. addShadows(); 13. 14. // Draws the filled circle in light blue 15. ctx.fill(); 16. 17. // Prepare for the outline 18. ctx.lineWidth = 5; 19. ctx.strokeStyle = "black"; 20. 21. // draws the path AGAIN (the circle), this 22. // time in wireframe 23. ctx.stroke(); 24. 25. // Notice we only once called context.arc()! And drew it twice 26. // with different styles 27. ... 28. 29. function addShadows() { 30. ctx.shadowColor = "Grey"; // color 31. ctx.shadowBlur = 20; // blur level 32. ctx.shadowOffsetX = 15; // horizontal offset 33. ctx.shadowOffsetY = 15; // vertical offset 34. }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Unwanted shadows - Example #2</title> 6. <style> 7. body { 8. margin: 0px; 9. padding: 0px; 10. } 11. #myCanvas { 12. border: 1px solid #9C9898; 13. } 14. </style> 15. <script> 16. var canvas, ctx; 17. window.onload = function(){ 18. canvas = document.getElementById("myCanvas"); 19. ctx = canvas.getContext("2d"); 20. var centerX = canvas.width / 2; 21. var centerY = canvas.height / 2; 22. var radius = 70; 23. ctx.beginPath(); 24. // Add to the path a full circle (from 0 to 2PI) 25. ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false); 26. // With path drawing you can change the context 27. // properties until a call to stroke() or fill() is performed 28. ctx.fillStyle = "lightBlue"; 29. // add shadows before drawing the filled circle 30. addShadows(); 31. // Draws the filled circle in light blue 32. ctx.fill(); 33. // Prepare for the outline 34. ctx.lineWidth = 5; 35. ctx.strokeStyle = "black"; 36. // draws AGAIN the path (the circle), this 37. // time in wireframe 38. ctx.stroke(); 39. // Notice we called context.arc() only once ! And drew it twice 40. // with different styles 41. }; 42. function addShadows() { 43. ctx.shadowColor = "Grey"; // color 44. ctx.shadowBlur = 20; // blur level 45. ctx.shadowOffsetX = 15; // horizontal offset 46. ctx.shadowOffsetY = 15; // vertical offset 47. } 48. </script> 49. </head> 50. <body> 51. <canvas id="myCanvas" width="578" height="200"> 52. </canvas> 53. </body> 54. </html>
Ah, indeed, the call to ctx.fill() casts a shadow, but the call to ctx.stroke(), that paints the whole path again, casts a shadow too, and this time the outline produces an unwanted shadow… How can we avoid this effect, while using the same technique for drawing the path?
The trick is to save the context before setting the shadow properties, then draw the filled circle, then restore the context (to its previous state: without shadows), then draw the outlined circle by calling ctx.stroke().
1. ... 2. // save the context before setting shadows and drawing the filled circle 3. ctx.save(); 4. 5. // With path drawing you can change the context 6. // properties until a call to stroke() or fill() is performed 7. ctx.fillStyle = "lightBlue"; 8. 9. // add shadows before drawing the filled circle 10. addShadows(); 11. 12. // Draws the filled circle in light blue 13. ctx.fill(); 14. 15. // restore the context to its previous saved state 16. ctx.restore(); 17. ...
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Unwanted shadows disappear- Example #2 bis</title> 6. <style> 7. body { 8. margin: 0px; 9. padding: 0px; 10. } 11. #myCanvas { 12. border: 1px solid #9C9898; 13. } 14. </style> 15. <script> 16. var canvas, ctx; 17. window.onload = function(){ 18. canvas = document.getElementById("myCanvas"); 19. ctx = canvas.getContext("2d"); 20. var centerX = canvas.width / 2; 21. var centerY = canvas.height / 2; 22. var radius = 70; 23. ctx.beginPath(); 24. // Add to the path a full circle (from 0 to 2PI) 25. ctx.arc(centerX, centerY, radius, 0, 2*Math.PI, false); 26. // save the context before setting shadows and drawing the filled circle 27. ctx.save(); 28. // With path drawing you can change the context 29. // properties until a call to stroke() or fill() is performed 30. ctx.fillStyle = "lightBlue"; 31. // add shadows before drawing the filled circle 32. addShadows(); 33. // Draws the filled circle in light blue 34. ctx.fill(); 35. // restore the context to its previous saved state 36. ctx.restore(); 37. // Prepare for the outline 38. ctx.lineWidth = 5; 39. ctx.strokeStyle = "black"; 40. // draws AGAIN the path (the circle), this 41. // time in wireframe 42. ctx.stroke(); 43. // Notice we called context.arc() only once ! And drew it twice 44. // with different styles 45. }; 46. function addShadows() { 47. ctx.shadowColor = "Grey"; // color 48. ctx.shadowBlur = 20; // blur level 49. ctx.shadowOffsetX = 15; // horizontal offset 50. ctx.shadowOffsetY = 15; // vertical offset 51. } 52. </script> 53. </head> 54. <body> 55. <canvas id="myCanvas" width="578" height="200"> 56. </canvas> 57. </body> 58. </html>
Several context properties can be used to set the thickness of the shape outlines, the way line end caps are drawn, etc.
They apply to all shapes that are drawn in path mode (lines, curves, arcs) and some also apply to rectangles.
We have seen this before. This is done by changing the value (in pixels) of the lineWidth property of the context:
ctx.lineWidth = 10; // set the thickness of every shape drawn in stroke/wireframe mode to 10 pixels
Here is a complete example where we draw with a lineWidth of 20 pixels:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>A simple example of lineWidth property use</title> 6. 7. </head> 8. <body> 9. <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas> 10. <script> 11. var canvas = document.getElementById('myCanvas'); 12. var ctx = canvas.getContext('2d'); 13. 14. // first path 15. ctx.moveTo(20, 20); 16. ctx.lineTo(100, 100); 17. ctx.lineTo(100, 0); 18. 19. 20. // second part of the path 21. ctx.moveTo(120, 20); 22. ctx.lineTo(200, 100); 23. ctx.lineTo(200, 0); 24. 25. // indicate stroke color + draw first part of the path 26. ctx.strokeStyle = "#0000FF"; 27. // Current line thickness is 20 pixels 28. ctx.lineWidth = 20; 29. ctx.stroke(); 30. 31. // Draws a rectangle 32. ctx.strokeRect(230, 10, 100, 100); 33. </script> 34. 35. </body> 36. </html>
The lineCap property of the context indicates the way line end caps are rendered. Possible values are butt (default), round, square (from top to bottom in the next illustration). Note that a value of “round” or “square” makes the lines slightly longer than the default value “butt”.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Changing the end caps of a line - Example #2</title> 6. </head> 7. <body> 8. <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas> 9. <script> 10. var canvas = document.getElementById('myCanvas'); 11. var ctx = canvas.getContext('2d'); 12. // first path 13. ctx.moveTo(20, 20); 14. ctx.lineTo(100, 100); 15. ctx.lineTo(100, 30); 16. // second part of the path 17. ctx.moveTo(120, 20); 18. ctx.lineTo(200, 100); 19. ctx.lineTo(200, 30); 20. // indicate stroke color + draw first part of the path 21. ctx.strokeStyle = "#0000FF"; 22. // Current line thickness is 20 pixels 23. ctx.lineWidth = 20; 24. // Try different values : butt, square, round 25. ctx.lineCap = "round"; 26. ctx.stroke(); 27. // Draws a rectangle 28. ctx.strokeRect(230, 10, 100, 100); 29. </script> 30. </body> 31. </html>
Note that in this example, the rectangle is not affected. It has no line ends visible - all its sides meet. However, the next property we’re going to look at will have an effect on rectangles!
The lineJoin property of the context indicates the way corners are rendered, when two lines meet. Possible values are miter (the default) for creating sharp corners, round, or bevel for “cut corners”.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Setting the type of corner when two lines meet - Example #3</title> 6. </head> 7. <body> 8. <canvas id="myCanvas" width="500">Your browser does not support the canvas tag.</canvas> 9. <script> 10. var canvas = document.getElementById('myCanvas'); 11. var ctx = canvas.getContext('2d'); 12. // first path 13. ctx.moveTo(20,20); 14. ctx.lineTo(100, 100); 15. ctx.lineTo(100,30); 16. // second part of the path 17. ctx.moveTo(120,20); 18. ctx.lineTo(200, 100); 19. ctx.lineTo(200,30); 20. // indicate stroke color + draw first part of the path 21. ctx.strokeStyle = "#0000FF"; 22. // Current line thickness is 20 pixels 23. ctx.lineWidth = 20; 24. // Try different values : miter(default), bevel, round 25. ctx.lineJoin="round"; 26. ctx.stroke(); 27. // Draws a rectangle 28. ctx.strokeRect(230, 10, 100, 100); 29. </script> 30. </body> 31. </html>
The miterLimit property value corresponds to the maximum miter length: the distance between the inner corner and the outer corner where two lines meet. When the angle of a corner between two lines gets smaller, the miter length grows and can become too long.
In order to avoid this situation, we can set the miterLimit property of the context to a threshold value. If the miter length exceeds the miterLimit value, then the corner will be rendered as if the lineJoin property had been set to “bevel” and the corner will be “cut”.
In the example, try different values for the miterLimit property. You’ll see that the way the corners are rendered changes at values around 2 and 3.
In order to perform an animation, we need to:
These are the basic steps for animating objects in a canvas. The order of the steps can be changed (i.e. you can move the shapes before drawing them), but, the principle is the same: clear-draw-move-repeat.
Step 1 could be avoided if you redraw the whole canvas content during step 2.
Even before HTML5 and the introduction of the canvas element, people created HTML games. They used CSS backgrounds inside <div> elements, and used to change the CSS top, left, width and height properties of the divs to animate graphic images on the screen.
During the late 1990s and early 2000s, JavaScript became increasingly popular. The community created a first ‘umbrella term’ describing a collection of technologies used together to create interactive and animated Web sites - DHTML (Dynamic HTML). For example, check the games developed at this time by Brent Silby (they all use DHTML).
For animation, the setInterval(function, ms) and setTimeout(function, ms) methods were the only solutions. Both methods take a function as the first parameter, and a number of milliseconds as the second parameter.
The only difference is that the code provided to setInterval will run every n milliseconds whereas the code in setTimeout will run only once after n milliseconds (meaning that we will have to repeat a call to setTimeout at step 4 above).
The methods described above are now completed by a new method that comes with multiple advantages: the requestAnimationFrame API.
We will compare the old methods with the new one, and implement the same example with each of them to highlight the differences.
Below is the example shown in the video, with source code:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Animation</title> 6. <style> 7. #myCanvas { 8. border: 1px solid black; 9. } 10. </style> 11. <script> 12. var canvas, ctx; 13. var rectangleX = 0; 14. var colors = ['red', 'blue', 'green']; 15. var currentColor = 0; 16. var speed = 3; 17. function init() { 18. // This function is called after the page is loaded 19. // 1 - Get the canvas 20. canvas = document.getElementById('myCanvas'); 21. // 2 - Get the context 22. ctx=canvas.getContext('2d'); 23. // 3 - we can draw 24. //setInterval(animate, 100); 25. //setTimeout(animate, 100); 26. requestAnimationFrame(animate); 27. setInterval(changeColor, 1000); 28. } 29. function animate() { 30. // - 1 clear canvas 31. ctx.clearRect(0, 0, canvas.width, canvas.height); 32. // 2 - draw a red rectangle 33. ctx.fillRect(rectangleX,0,80,100); 34. // 3 - move the shapes 35. rectangleX = rectangleX +speed; 36. if((rectangleX+80 > 200) || (rectangleX <= 0)){ 37. speed = -speed; 38. } 39. // Call again the anilate function after 100ms 40. //setTimeout(animate, 100); 41. requestAnimationFrame(animate); 42. } 43. function changeColor() { 44. ctx.fillStyle= colors[currentColor%3]; 45. currentColor += 1; 46. // Comment next line if you do not want to change the speed at 47. // each bounce 48. speed += Math.sign(speed)*1; 49. console.log("speed = " + speed) 50. } 51. </script> 52. </head> 53. <body onload="init();"> 54. <canvas id="myCanvas" width="200" height="200"> 55. Your browser does not support the canvas tag. 56. </canvas> 57. </body> 58. </html>
Errata: in the video, we use speed +=1; in order to increment the speed of the rectangle each time it bounces (in the changeColor() function). This is not correct as speed can be negative. The online example fixes this by using speed += Math.sign(speed) * 1; instead this will add +1 or -1 depending on the sign of speed.
The setInterval(…) function is still popular on the Web, and even though this is not the recommended way to do 60 frames/second canvas animation, it is worth understanding how it works.
Syntax: setInterval(function, ms);
The setInterval(…) function calls another function or evaluates an expression at specified intervals of time (in milliseconds), and returns the unique id of the action. You can always stop it by calling the clearInterval(id) function with the interval identifier as an argument.
This is how pre-HTML5 games were written. Before the introduction of the canvas element, developers made games using div elements. By changing their background color, top and left CSS positions, it was possible to animate characters in games. The animation was created by calling repeatedly a function that did the drawing, using the JavaScript setInterval or setTimeout functions.
Please try this example that moves/animates a div using setInterval:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>setInterval for animation</title> 6. <style> 7. #animatedDIV { 8. position:absolute; 9. background-color:red; 10. color:white; 11. height:100px; 12. } 13. </style> 14. </head> 15. <body> 16. <button onclick="start()">Start animation</button> 17. <button onclick="stop()">Stop animation</button> 18. <div> 19. <div id="animatedDIV">Animated DIV :-)</div> 20. </div> 21. <script> 22. var elm = document.getElementById("animatedDIV"); 23. var requestId; 24. var x = 0; 25. 26. function render(time) { 27. elm.style.left = x++ + "px"; 28. } 29. 30. function start() { 31. requestId = setInterval(render, 10); 32. } 33. 34. function stop() { 35. if (requestId) { 36. clearInterval(requestId); 37. } 38. } 39. </script> 40. </body> 41. </html>
1. <body> 2. <div id="animatedDIV">Animated DIV :-)</div> 3. 4. <button onclick="start()">Start animation</button> 5. <button onclick="stop()">Stop animation</button> 6. 7. <script> 8. var elm = document.getElementById("animatedDIV"); 9. var requestId; 10. var x = 0; 11. 12. function render(time) { 13. elm.style.left = x++ + "px"; 14. } 15. 16. function start() { 17. r/equestId = setInterval(render, 10); 18. } 19. 20. function stop() { 21. if (requestId) { 22. clearInterval(requestId); 23. } 24. } 25. </script> 26. </body>
Here, we define a <div> element, (see the online source code for the CSS properties involved), and we use the setInterval method (line 17) to call every 10ms the render() method that will just increment the position of this element. Notice that since we’re using the DOM, the horizontal position of the div is modified by changing its left CSS property.
The call to setInterval returns an id we can use to stop the animation, by calling clearInterval (line 22).
We use the drawMonster() function:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>setInterval and monster animation</title> 6. <style> 7. #myCanvas { 8. border: 1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas" width="400" height="400"> 14. Your browser does not support the canvas tag. 15. </canvas> 16. <p> 17. <button onclick="start()">Start animation</button> 18. <button onclick="stop()">Stop animation</button> 19. <script> 20. var canvas, ctx; 21. var monsterX=100, monsterY=100, monsterAngle=0; 22. 23. function init() { 24. // This function is called after the page is loaded 25. // 1 - Get the canvas 26. canvas = document.getElementById('myCanvas'); 27. // 2 - Get the context 28. ctx=canvas.getContext('2d'); 29. } 30. 31. function animationLoop() { 32. // 1 - Clear the canvas 33. ctx.clearRect(0, 0, canvas.width, canvas.height); 34. // 2 Draw the monster using variables for pos, angle, etc. 35. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 36. // 3 Move the monster (change pos, angle, size, etc.) 37. monsterX += 10; 38. monsterX %= canvas.width 39. monsterAngle+= 0.01; 40. } 41. 42. function drawMonster(x, y, angle, headColor, eyeColor) { 43. // GOOD PRACTICE : SAVE CONTEXT AND RESTORE IT AT THE END 44. ctx.save(); 45. // Moves the coordinate system so that the monster is drawn 46. // at position (x, y) 47. ctx.translate(x, y); 48. ctx.rotate(angle) 49. // head 50. ctx.fillStyle=headColor; 51. ctx.fillRect(0,0,200,200); 52. // eyes 53. ctx.fillStyle='red'; 54. ctx.fillRect(35,30,20,20); 55. ctx.fillRect(140,30,20,20); 56. // interior of eye 57. ctx.fillStyle=eyeColor; 58. ctx.fillRect(43,37,10,10); 59. ctx.fillRect(143,37,10,10); 60. // Nose 61. ctx.fillStyle='black'; 62. ctx.fillRect(90,70,20,80); 63. // Mouth 64. ctx.fillStyle='purple'; 65. ctx.fillRect(60,165,80,20); 66. // GOOD PRACTICE ! 67. ctx.restore(); 68. } 69. 70. function start() { 71. // Start the animation loop, change 20 for bigger values 72. requestId = setInterval(animationLoop, 20); 73. } 74. 75. function stop() { 76. if (requestId) { 77. clearInterval(requestId); 78. } 79. } 80. </script> 81. </body> 82. </html>
1. <body onload="init();"> 2. <canvas id="myCanvas" width="400" height="400"> 3. Your browser does not support the canvas tag. 4. </canvas> 5. <p> 6. <button onclick="start()">Start animation</button> 7. <button onclick="stop()">Stop animation</button> 8. 9. <script> 10. var canvas, ctx; 11. var monsterX=100, monsterY=100, monsterAngle=0; 12. 13. function init() { 14. // This function is called after the page is loaded 15. // 1 - Get the canvas 16. canvas = document.getElementById('myCanvas'); 17. // 2 - Get the context 18. ctx=canvas.getContext('2d'); 19. } 20. 21. function animationLoop() { 22. // 1 - Clear the canvas 23. ctx.clearRect(0, 0, canvas.width, canvas.height); 24. 25. // 2 Draw the monster using variables for pos, angle, etc. 26. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 27. 28. // 3 Move the monster (change pos, angle, size, etc.) 29. monsterX += 10; 30. monsterX %= canvas.width 31. monsterAngle+= 0.01; 32. } 33. 34. function drawMonster(x, y, angle, headColor, eyeColor) { 35. // BEST PRACTICE: SAVE CONTEXT AND RESTORE IT AT THE END 36. ctx.save(); 37. 38. // Moves the coordinate system so that the monster is drawn 39. // at position (x, y) 40. ctx.translate(x, y); 41. ctx.rotate(angle) 42. 43. // head 44. ctx.fillStyle=headColor; 45. ctx.fillRect(0,0,200,200); 46. ... 47. 48. // BEST PRACTICE! 49. ctx.restore(); 50. } 51. 52. function start() { 53. // Start the animation loop, change 20 for bigger values 54. requestId = setInterval(animationLoop, 20); 55. } 56. 57. function stop() { 58. if (requestId) { 59. clearInterval(requestId); 60. } 61. } 62. </script> 63. </body>
While the above example works, there are several reasons not to use setInterval for doing smooth animations.
The setInterval function may become hard to debug, particularly if you run several animations simultaneously. For example, if you have two intervals, one running every 100 milliseconds, the other every second, and if you want to debug the second one, the first one will constantly be run at regular intervals, making step by step debugging really difficult.
setInterval will execute the function passed as first parameter every n milliseconds regardless of when the function was last called or how long the function takes to execute. If the function takes longer than the interval, then setInterval might queue too many function executions back to back when the interval is too short, leading to unpredictable results.
BEST PRACTICE: AVOID using setInterval for animating in a canvas, except for trivial cases (change a color every second).
One thing you should always remember about using setInterval: if we set number of milliseconds at - let’s say 20ms - it will call our game loop function EACH 20ms, even if the previous one is not yet finished. This may lead to many problems (incomplete rendering, etc.).
That’s where we can use another function:
This function works like setInterval(…) with one difference: it calls your function ONCE and AFTER a given amount of time.
Check the example below (click on start animation):
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Monster animated in a canvas with setTimeout</title> 6. <style type="text/css"> 7. canvas { 8. border:1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas" width="400" height="400"> 14. Your browser does not support the canvas tag. 15. </canvas> 16. <p> 17. <button onclick="start()">Start animation</button> 18. <button onclick="stop()">Stop animation</button> 19. <script> 20. var canvas, ctx; 21. var monsterX=100, monsterY=100, monsterAngle=0; 22. 23. function init() { 24. // This function is called after the page is loaded 25. // 1 - Get the canvas 26. canvas = document.getElementById('myCanvas'); 27. // 2 - Get the context 28. ctx=canvas.getContext('2d'); 29. } 30. 31. function animationLoop() { 32. // 1 - Clear 33. ctx.clearRect(0, 0, canvas.width, canvas.height); 34. // 2 Draw 35. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 36. // 3 Move 37. monsterX += 10; 38. monsterX %= canvas.width 39. monsterAngle+= 0.01; 40. // call again mainloop after 20ms 41. requestId = setTimeout(animationLoop, 20); 42. } 43. 44. function drawMonster(x, y, angle, headColor, eyeColor) { 45. // GOOD PRACTICE : SAVE CONTEXT AND RESTORE IT AT THE END 46. ctx.save(); 47. // Moves the coordinate system so that the monster is drawn 48. // at position (x, y) 49. ctx.translate(x, y); 50. ctx.rotate(angle) 51. // head 52. ctx.fillStyle=headColor; 53. ctx.fillRect(0,0,200,200); 54. // eyes 55. ctx.fillStyle='red'; 56. ctx.fillRect(35,30,20,20); 57. ctx.fillRect(140,30,20,20); 58. // interior of eye 59. ctx.fillStyle=eyeColor; 60. ctx.fillRect(43,37,10,10); 61. ctx.fillRect(143,37,10,10); 62. // Nose 63. ctx.fillStyle='black'; 64. ctx.fillRect(90,70,20,80); 65. // Mouth 66. ctx.fillStyle='purple'; 67. ctx.fillRect(60,165,80,20); 68. // GOOD PRACTICE ! 69. ctx.restore(); 70. } 71. 72. function start() { 73. // Start the animation loop, change 20 for bigger 74. // values 75. requestId = setTimeout(animationLoop, 20); 76. } 77. 78. function stop() { 79. if (requestId) { 80. clearTimeout(requestId); 81. } 82. } 83. </script> 84. </body> 85. </html>
This is similar to the previous example except that we called setTimeout(function, delay) instead of setInterval(function, period). As setTimeout runs the function passed as the first parameter only once, we also have to call it at the end of the loop.
1. function animationLoop() { 2. // 1 - Clear 3. ctx.clearRect(0, 0, canvas.width, canvas.height); 4. 5. // 2 Draw 6. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 7. 8. // 3 Move 9. monsterX += 10; 10. monsterX %= canvas.width 11. monsterAngle+= 0.01; 12. 13. // call mainloop again after 20ms 14. requestId = setTimeout(animationLoop, 20); 15. } 16. 17. function start() { 18. // Start the animation loop, change 20 for bigger 19. // values 20. requestId = setTimeout(animationLoop, 20); 21. } 22. 23. function stop() { 24. if (requestId) { 25. clearTimeout(requestId); 26. } 27. }
This function is certainly more suitable for doing graphic animation, such as for writing an HTML5 game. It will never interrupt an ongoing animation, even if the instructions inside the animation loop take too long.
setTimeout does not “wait” during the timeout period. It lets the rest of the JavaScript code run. It schedules a new call to the function passed as first parameter with a timer running in the background. This might cause it to take slightly longer than the expected timeout period to start executing.
This problem also occurs with setInterval, the timing is not “very” reliable. If you plan to run a function every 20ms, and if you measure precisely the real timing, sometimes you will discover big differences between what is scheduled and what is performed. This is because these methods were designed a long time ago, when high precision timers and 60 frames per second animation were not an option.
Here comes the requestAnimationFrame API, a very good companion to the canvas API!
BEST PRACTICE: AVOID using setTimeout for animating in a canvas, except for trivial cases.
For 60 frames/second animation, use requestAnimationFrame!
The best way to make animation at 60 frames per second: requestAnimationFrame!
The requestAnimationFrame(animationLoop) is very similar to setTimeout:
It has, however, several advantages over setInterval and setTimeout:
You will note that requestAnimationFrame(function) is used like setTimeout(function, delay). A call to requestAnimationFrame just asks the browser to call the function passed as a parameter ONCE, and the target delay is fixed, and corresponds to a 60 frames/s frame rate (16.6ms). Notice that an id is used for stopping an animation with cancelAnimationFrame(id).
1. <body onload="init();"> 2. <script> 3. var canvas, ctx; 4. 5. function init() { 6. // This function is called after the page is loaded 7. // 1 - Get the canvas 8. canvas = document.getElementById('myCanvas'); 9. // 2 - Get the context 10. ctx=canvas.getContext('2d'); 11. 12. // 3 - start the animation 13. startAnimation(); 14. } 15. 16. var id; 17. function animationLoop(timeStamp) { 18. // 1 - Clear 19. ctx.clearRect(0, 0, canvas.width, canvas.height); 20. // 2 Draw 21. drawShapes(...); 22. // 3 Move 23. moveShapes(...); 24. // call mainloop again after 16.6ms (corresponds to 60 frames/second) 25. id = requestAnimationFrame(animationLoop); 26. } 27. 28. function startAnimation() { 29. id = requestAnimationFrame(animationLoop); 30. } 31. function stopAnimation() { 32. if (id) { 33. cancelAnimationFrame(id); 34. } 35. } 36. </script> 37. </body>
1. function animationLoop(timeStamp) { 2. // 1 - Clear 3. ctx.clearRect(0, 0, canvas.width, canvas.height); 4. 5. // 2 - Draw 6. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 7. 8. // 3 - Move 9. monsterX += 10; 10. monsterX %= canvas.width 11. monsterAngle+= 0.01; 12. 13. // call mainloop again after 16.6 ms (60 frames/s) 14. requestId = requestAnimationFrame(animationLoop); 15. } 16. 17. function start() { 18. // Start the animation loop, targets 60 frames/s, this 19. // calls animationLoop only ONCE! 20. requestId = requestAnimationFrame(animationLoop); 21. } 22. 23. function stop() { 24. if (requestId) { 25. cancelAnimationFrame(requestId); 26. } 27. }
Notice that calling requestAnimationFrame(animationLoop) at line 19, and after that from within the loop at line 14, asks the browser to call the animationLoop function so that the delta between calls will be as close as possible to 16.6ms (this corresponds to 1/60th of a second).
This target may be hard to reach if:
Many HTML5 games perform what we call a “time-based animation”. For this, we need an accurate timer that will tell us the elapsed time between each animation frame.
Depending on this time, we can compute the distances that must be achieved by each object on the screen in order to move at a constant speed (for a human eye), independently of the CPU or GPU of the computer or mobile device that is running the game.
The timeStamp parameter of the animationLoop function (line 1 in the above code) is useful for exactly that: it gives a high resolution time. By measuring deltas between two consecutive calls of the animationLoop, we will know exactly, with a sub-millisecond accuracy, the elapsed time between two frames.
Using time-based animation, and more generally, using the canvas element for writing HTML5 games, is part of the W3Cx HTML5 Apps and Games course.
Current support is really good and all modern browsers support this API.
In JavaScript, we treat events made by users as an input, and we manipulate the DOM structure as an output. Most of the time in games/animations, we will change state variables of moving objects, such as position or speed of an alien ship, and the animation loop will take care of these variables to move the objects.
The events are called DOM events, and we use the DOM JavaScript API to create event handlers.
You will often find this in examples on the Web:
1. <div id="someDiv" onclick="alert('clicked!');"> 2. content of the div 3. </div>
Note: this is not the recommended way to handle events, even if it’s very easy to use. Mixing the ‘visual layer’ (HTML) and the ‘logic layer’ (JavaScript) in one place is ok for small examples (we have used this in some examples in this course) but is not the recommended way for full scale applications where a clean separation is best.
1. document.getElementById('someDiv').onclick = function(evt) { 2. alert('clicked!'); 3. }
This method is fine, but you will not be able to attach several listener functions. If you need to do this, the preferred version is the next one.
1. document.getElementById('someDiv').addEventListener('click', function(evt) { 2. alert('clicked!'); 3. }, false);
The third parameter is not important for now, just set it to false, or simply do not add a third parameter.
When you create an EventListener and attach it to an element, an event object will be passed as a parameter to your callback, just like this:
1. element.addEventListener('click', function(<b>event</b>) { 2. <b>// now you can use the event object inside the callback</b> 3. }, false);
Depending on the type of event you are listening to, we will use different properties from the event object in order to get useful information like: “what keys have been pressed down?”, “what is the position of the mouse cursor?”, “which mouse button is down?”, etc.
Let’s see next how to deal with the keyboard and the mouse. In the W3Cx HTML5 Apps and Games, we look at additional APIs such as the gamePad API for using USB or wireless gamepads/joysticks/game controllers.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Click on button</title> 6. </head> 7. <body> 8. <button id="myButton">Click me!</button> 9. <script> 10. var button = document.getElementById('myButton'); 11. // Define a click listener on the button 12. button.addEventListener('click', processClick; 13. 14. // callback 15. function processClick(event) { 16. console.log("Button clicked"); 17. 18. // What is the event parameter? 19. } 20. </script> 21. </body> 22. </html>
When you listen to keyboard related events (keydown, keyup or keypressed), the event parameter passed to the listener function will contain the code of the key that fired the event. Then it is possible to test what key has been pressed or released, like this:
1. window.addEventListener('keydown', function(event) { 2. if (event.keyCode === 37) { 3. //left arrow was pressed 4. } 5. }, false);
At line 2, the value “37” is the key code that corresponds to the left arrow. It might be difficult to know the correspondences between real keyboard keys and codes, so here are handy pointers:
A lot of people think that the canvas element is not able to get key events. Many examples on the Web handle key events on canvas by adding a listener to the window object directly, like this:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Adding a key listener to the window object - Example #1</title> <style> #myCanvas { border: 1px solid black; } </style> <body onload="init();"> <p>
This example shows how to handle key events in a canvas by adding a key listener to the window object. Like that, all key press in the document will trigger the event listener function.
1. <canvas id="myCanvas" width="350" height="200"> 2. </canvas> 3. <script> 4. var canvas; 5. function init() { 6. // This will work when you press a key, anywhere on the document 7. window.addEventListener('keydown', handleKeydown, false); 8. } 9. function handleKeydown(e){ 10. alert('keycode: '+e.keyCode); 11. return false; 12. }; 13. </script> 14. </body> 15. </html>
1. <canvas id="myCanvas" width="350" height="200"> 2. </canvas> 3. 4. <script> 5. 6. function init() { 7. // This will work when you press a key, anywhere on the document 8. window.addEventListener('keydown', handleKeydown, false); 9. } 10. 11. 12. function handleKeydown(e){ 13. alert('keycode: '+e.keyCode); 14. return false; 15. }; 16. </script>
Indeed this solution works well if you write a game, and want to detect events wherever the mouse cursor is, and without worrying about what HTML element has the focus, etc…
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Moving the monster with the keyboard - Example #2</title> 6. <style type="text/css"> 7. canvas { 8. border:1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas" width="400" height="400"> 14. Your browser does not support the canvas tag. 15. </canvas> 16. <p> 17. <script> 18. var canvas, ctx; 19. var monsterX=100, monsterY=100, monsterAngle=0; 20. var incrementX = 0; 21. function init() { 22. // This function is called after the page is loaded 23. // 1 - Get the canvas 24. canvas = document.getElementById('myCanvas'); 25. // 2 - Get the context 26. ctx=canvas.getContext('2d'); 27. // 3 add key listeners to the window element 28. window.addEventListener('keydown', handleKeydown, false); 29. window.addEventListener('keyup', handleKeyup, false); 30. // 4 - start the animation 31. requestId = requestAnimationFrame(animationLoop); 32. } 33. 34. function handleKeydown(evt) { 35. if (evt.keyCode === 37) { 36. //left key 37. incrementX = -1; 38. } else if (evt.keyCode === 39) { 39. // right key 40. incrementX = 1; 41. } 42. } 43. 44. function handleKeyup(evt) { 45. incrementX = 0; 46. } 47. 48. function animationLoop() { 49. // 1 - Clear 50. ctx.clearRect(0, 0, canvas.width, canvas.height); 51. // 2 Draw 52. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 53. // 3 Move 54. monsterX += incrementX; 55. // call again mainloop after 16.6 ms (60 frames/s) 56. requestId = requestAnimationFrame(animationLoop); 57. } 58. function drawMonster(x, y, angle, headColor, eyeColor) { 59. // GOOD PRACTICE : SAVE CONTEXT AND RESTORE IT AT THE END 60. ctx.save(); 61. // Moves the coordinate system so that the monster is drawn 62. // at position (x, y) 63. ctx.translate(x, y); 64. ctx.rotate(angle) 65. // head 66. ctx.fillStyle=headColor; 67. ctx.fillRect(0,0,200,200); 68. // eyes 69. ctx.fillStyle='red'; 70. ctx.fillRect(35,30,20,20); 71. ctx.fillRect(140,30,20,20); 72. // interior of eye 73. ctx.fillStyle=eyeColor; 74. ctx.fillRect(43,37,10,10); 75. ctx.fillRect(143,37,10,10); 76. // Nose 77. ctx.fillStyle='black'; 78. ctx.fillRect(90,70,20,80); 79. // Mouth 80. ctx.fillStyle='purple'; 81. ctx.fillRect(60,165,80,20); 82. // GOOD PRACTICE ! 83. ctx.restore(); 84. } 85. function start() { 86. // Start the animation loop, targets 60 frames/s 87. requestId = requestAnimationFrame(animationLoop); 88. } 89. function stop() { 90. if (requestId) { 91. cancelAnimationFrame(requestId); 92. } 93. } 94. </script> 95. </body> 96. </html>
1. <script> 2. var canvas, ctx; 3. var monsterX=100, monsterY=100, monsterAngle=0; 4. var incrementX = 0; 5. 6. function init() { 7. // This function is called after the page is loaded 8. 9. // 1 - Get the canvas 10. canvas = document.getElementById('myCanvas'); 11. 12. // 2 - Get the context 13. ctx=canvas.getContext('2d'); 14. 15. // 3 add key listeners to the window element 16. window.addEventListener('keydown', handleKeydown, false); 17. window.addEventListener('keyup', handleKeyup, false); 18. 19. // 4 - start the animation 20. requestId = requestAnimationFrame(animationLoop); 21. } 22. 23. function handleKeydown(evt) { 24. if (evt.keyCode === 37) { 25. //left key 26. incrementX = -1; 27. } else if (evt.keyCode === 39) { 28. // right key 29. incrementX = 1; 30. } 31. } 32. 33. function handleKeyup(evt) { 34. incrementX = 0; 35. } 36. 37. function animationLoop() { 38. // 1 - Clear 39. ctx.clearRect(0, 0, canvas.width, canvas.height); 40. 41. // 2 Draw 42. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 43. 44. // 3 Move 45. monsterX += incrementX; 46. 47. // call again mainloop after 16.6 ms (60 frames/s) 48. requestId = requestAnimationFrame(animationLoop); 49. } 50. </script>
If you add a key listener to a canvas element, the problem is that it will get events only when it has the focus. And by default, it will never have the focus!
The tabindex attribute of the canvas element makes it focusable. Without it, it will never get the focus!
The trick is to declare the canvas like this:
1. <canvas id="myCanvas" width="350" tabindex="1" height="200"> 2. </canvas>
1. canvas=document.getElementById('myCanvas'); 2. ... 3. canvas.focus();
Now, if we try an example with the above canvas declaration, we show when an HTML element has the focus: a border is added to it.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Listening to key events only in the canvas - Example #3</title> 6. <style type="text/css"> 7. canvas { 8. border:1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. This example shows how to handle key events in a canvas by adding a key listener to the canvas object 14. (see the blue border around the canvas... it shows that the canvas has the focus. Try to click outside 15. of the canvas: it will lose the focus). 16. <p> 17. If you don't want a border to appear when you click on the canvas, 18. set its style to outline: none 19. </p> 20. <p>Press a key when the canvas has the focus: an alert will display the keycode of the key you pressed. 21. Problem: if the canvas looses the focus, key press are no more detected...</p> 22. <canvas id="myCanvas" width="350" 23. tabindex="1" height="200"> 24. </canvas> 25. <script> 26. var canvas; 27. function init() { 28. canvas=document.getElementById('myCanvas'); 29. // This will work only if the canvas has the focus 30. canvas.addEventListener('keydown', handleKeydown, false); 31. // We can set the focus on the canvas like that: 32. //canvas.focus(); 33. // ... but it will stop working if we click somewhere else 34. // in the document 35. } 36. function handleKeydown(e){ 37. alert('keycode: '+e.keyCode); 38. return false; 39. }; 40. </script> 41. </body> 42. </html>
Note that the line that forces the focus to the canvas is commented by default. Try to click on the canvas, then press a key, then click out of the canvas, then press a key: this time nothing happens!
1. var canvas; 2. 3. function init() { 4. canvas=document.getElementById('myCanvas'); 5. 6. // This will work only if the canvas has the focus 7. canvas.addEventListener('keydown', handleKeydown, false); 8. 9. // We can set the focus on the canvas like this: 10. //canvas.focus(); 11. 12. // ... but it will stop working if we click somewhere else 13. // in the document 14. } 15. 16. 17. function handleKeydown(e){ 18. alert('keycode: '+e.keyCode); 19. return false; 20. };
Line 10 is useful to initially set the focus on the canvas, but this trick will not work if we click somewhere else in the HTML page.
A better way to manage key events on a canvas is to set the focus when the mouse is over the canvas, and to un-focus it otherwise.
Here is a modified version of the “move monster example” seen earlier. This time, you move the monster with the left and right arrow only when the mouse cursor is over the canvas. We added two mouse event listeners on the canvas: one for the mouseenter event and the other for the mouseout event.
When the mouse enters the canvas we call canvas.focus() to set the focus to the canvas, and when the mouse cursor goes out of the canvas, we call canvas.blur() to unset the focus.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Setting the focus when the mouse cursor enters the canvas - Example #4</title> 6. <style type="text/css"> 7. canvas { 8. border:1px solid black; 9. } 10. </style> 11. </head> 12. <body onload="init();"> 13. <canvas id="myCanvas" tabindex="1" width="400" height="400"> 14. Your browser does not support the canvas tag. 15. </canvas> 16. <p> 17. <script> 18. var canvas, ctx; 19. var monsterX=100, monsterY=100, monsterAngle=0; 20. var incrementX = 0; 21. function init() { 22. // This function is called after the page is loaded 23. // 1 - Get the canvas 24. canvas = document.getElementById('myCanvas'); 25. // 2 - Get the context 26. ctx=canvas.getContext('2d'); 27. // 3 add key listeners to the window element 28. canvas.addEventListener('keydown', handleKeydown, false); 29. canvas.addEventListener('keyup', handleKeyup, false); 30. canvas.addEventListener('mouseenter', setFocus, false); 31. canvas.addEventListener('mouseout', unsetFocus, false); 32. // 4 - start the animation 33. requestId = requestAnimationFrame(animationLoop); 34. } 35. function setFocus(evt) { 36. canvas.focus(); 37. }; 38. function unsetFocus(evt) { 39. canvas.blur(); 40. incrementX = 0; // I added this line 41. }; 42. function handleKeydown(evt) { 43. if (evt.keyCode === 37) { 44. //left key 45. incrementX = -1; 46. } else if (evt.keyCode === 39) { 47. // right key 48. incrementX = 1; 49. } 50. } 51. function handleKeyup(evt) { 52. incrementX = 0; 53. } 54. function animationLoop() { 55. // 1 - Clear 56. ctx.clearRect(0, 0, canvas.width, canvas.height); 57. // 2 Draw 58. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 59. // 3 Move 60. monsterX += incrementX; 61. // call again mainloop after 16.6 ms (60 frames/s) 62. requestId = requestAnimationFrame(animationLoop); 63. } 64. function drawMonster(x, y, angle, headColor, eyeColor) { 65. // GOOD PRACTICE : SAVE CONTEXT AND RESTORE IT AT THE END 66. ctx.save(); 67. // Moves the coordinate system so that the monster is drawn 68. // at position (x, y) 69. ctx.translate(x, y); 70. ctx.rotate(angle) 71. // head 72. ctx.fillStyle=headColor; 73. ctx.fillRect(0,0,200,200); 74. // eyes 75. ctx.fillStyle='red'; 76. ctx.fillRect(35,30,20,20); 77. ctx.fillRect(140,30,20,20); 78. // interior of eye 79. ctx.fillStyle=eyeColor; 80. ctx.fillRect(43,37,10,10); 81. ctx.fillRect(143,37,10,10); 82. // Nose 83. ctx.fillStyle='black'; 84. ctx.fillRect(90,70,20,80); 85. // Mouth 86. ctx.fillStyle='purple'; 87. ctx.fillRect(60,165,80,20); 88. // GOOD PRACTICE ! 89. ctx.restore(); 90. } 91. function start() { 92. // Start the animation loop, targets 60 frames/s 93. requestId = requestAnimationFrame(animationLoop); 94. } 95. function stop() { 96. if (requestId) { 97. cancelAnimationFrame(requestId); 98. } 99. } 100. </script> 101. </body> 102. </html>
1. function init() { 2. // This function is called after the page is loaded 3. // 1 - Get the canvas 4. canvas = document.getElementById('myCanvas'); 5. // 2 - Get the context 6. ctx=canvas.getContext('2d'); 7. 8. // 3 - Add key listeners to the window element 9. canvas.addEventListener('keydown', handleKeydown, false); 10. canvas.addEventListener('keyup', handleKeyup, false); 11. 12. canvas.addEventListener('mouseenter', setFocus, false); 13. canvas.addEventListener('mouseout', unsetFocus, false); 14. 15. // 4 - Start the animation 16. requestId = requestAnimationFrame(animationLoop); 17. } 18. 19. function setFocus(evt) { 20. canvas.focus(); 21. }; 22. 23. 24. function unsetFocus(evt) { 25. canvas.blur(); 26. incrementX = 0; // stop the monster if the mouse exists the canvas 27. };
The third parameter (false) of lines 12 and 13 means “we do not want to propagate the event to the ancestors of the canvas in the DOM.”
Detecting mouse events in a canvas is quite straightforward: you add an event listener to the canvas, and the browser invokes that listener when the event occurs.
The example below is about listening to mouseup and mousedown events (when a user presses or releases any mouse button):
1. canvas.addEventListener('mousedown', function (evt) { 2. // do something with to the mousedown event 3. });
The event received by the listener function will be used for getting the button number or the coordinates of the mouse cursor. Before looking at different examples, let’s look at the different event types we can listen to.
We saw in the last example how to detect the mouseenter and mouseout events.
There are other events related to the mouse:
When you listen to any of the above events, the event object (we call it a “DOM event”), passed to the listener function, has properties that correspond to the mouse coordinates: clientX and clientY.
However, these are what we call “window coordinates”. Instead of being relative to the canvas itself, they are relative to the window (the page).
Most of the time you need to work with the mouse position relative to the canvas, not to the window, so you must convert the coordinates between the window and the canvas. This will take into account the position of the canvas, and the CSS properties that may affect the canvas position (margin, etc.).
Fortunately, there exists a method for getting the position and size of any element in the page: getBoundingClientRect().
Play with the example below that show the problem:
var canvas, ctx, mousePos, mouseButton; window.onload = function init() { canvas = document.getElementById('myCanvas'); ctx = canvas.getContext('2d'); canvas.addEventListener('mousemove', function (evt) { mousePos = getMousePos(canvas, evt); var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y; writeMessage(canvas, message); }, false); canvas.addEventListener('mousedown', function (evt) { mouseButton = evt.button; var message = "Mouse button " + evt.button + " down at position: " + mousePos.x + ',' + mousePos.y; writeMessage(canvas, message); }, false); canvas.addEventListener('mouseup', function (evt) { var message = "Mouse up at position: " + mousePos.x + ',' + mousePos.y; writeMessage(canvas, message); }, false); }; function writeMessage(canvas, message) { ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.font = '18pt Calibri'; ctx.fillStyle = 'black'; ctx.fillText(message, 10, 25); ctx.restore(); } function getMousePos(canvas, evt) { // necessary to take into account CSS boudaries var rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; }
canvas { border 1px solid black }
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Mouse event - Good code</title> 6. <style> 7. body { 8. margin: 20px; 9. padding: 0px; 10. } 11. </style> 12. </head> 13. <body> 14. This is a canvas:<p></p> 15. <canvas id="myCanvas" width="578" height="200"></canvas> 16. </body> 17. </html>
1. function getMousePos(canvas, evt) { 2. // necessary to take into account CSS boundaries 3. var rect = canvas.getBoundingClientRect(); 4. return { 5. x: evt.clientX - rect.left, 6. y: evt.clientY - rect.top 7. }; 8. }
1. ... 2. canvas.addEventListener('mousemove', function (evt) { 3. mousePos = getMousePos(canvas, evt); 4. var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y; 5. writeMessage(canvas, message); 6. }, false); 7. 8. ... 9. function getMousePos(canvas, evt) { 10. // WRONG!!! 11. return { 12. x: evt.clientX, 13. y: evt.clientY 14. }; 15. }
function getMousePos(canvas, evt) { // necessary to take into account CSS boundaries var rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; }
This example uses the previous function for computing the mouse position correctly. It listens to mousemove, mousedown and mouseup events, and shows how to get the mouse button number using the evt.button property.
1. var canvas, ctx, mousePos, mouseButton; 2. 3. window.onload = function init() { 4. canvas = document.getElementById('myCanvas'); 5. ctx = canvas.getContext('2d'); 6. 7. canvas.addEventListener('mousemove', function (evt) { 8. mousePos = getMousePos(canvas, evt); 9. var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y; 10. writeMessage(canvas, message); 11. }, false); 12. 13. canvas.addEventListener('mousedown', function (evt) { 14. mouseButton = evt.button; 15. var message = "Mouse button " + evt.button + " down at position: " + mousePos.x + ',' + mousePos.y; 16. writeMessage(canvas, message); 17. }, false); 18. 19. canvas.addEventListener('mouseup', function (evt) { 20. var message = "Mouse up at position: " + mousePos.x + ',' + mousePos.y; 21. writeMessage(canvas, message); 22. }, false); 23. }; 24. 25. function writeMessage(canvas, message) { 26. ctx.save(); 27. ctx.clearRect(0, 0, canvas.width, canvas.height); 28. ctx.font = '18pt Calibri'; 29. ctx.fillStyle = 'black'; 30. ctx.fillText(message, 10, 25); 31. ctx.restore(); 32. } 33. 34. function getMousePos(canvas, evt) { 35. // necessary to take into account CSS boudaries 36. var rect = canvas.getBoundingClientRect(); 37. return { 38. x: evt.clientX - rect.left, 39. y: evt.clientY - rect.top 40. }; 41. }
This example shows an animation at 60 frames/s using requestAnimationFrame, were the monster is drawn at the mouse position, and if a mouse button is pressed, the monster starts rotating around its center. If we release the mouse button, the rotation stops:
1. var canvas, ctx; 2. var monsterX=100, monsterY=100, monsterAngle=0; 3. var incrementX = 0; 4. var incrementAngle =0; 5. var mousePos; 6. 7. function init() { 8. ... 9. // 3bis - Add mouse listeners 10. canvas.addEventListener('mousemove', handleMousemove, false); 11. canvas.addEventListener('mousedown', handleMousedown, false); 12. canvas.addEventListener('mouseup', handleMouseup, false); 13. // 4 - Start the animation 14. requestId = requestAnimationFrame(animationLoop); 15. } 16. function handleMousemove(evt) { 17. // The mousePos will be taken into account in the animationLoop 18. mousePos = getMousePos(canvas, evt); 19. } 20. function handleMousedown(evt) { 21. // the increment on the angle will be 22. // taken into account in the animationLoop 23. incrementAngle = 0.1; 24. } 25. function handleMouseup(evt) { 26. incrementAngle = 0; // stops the rotation 27. } 28. function getMousePos(canvas, evt) { 29. ... // same as before 30. } 31. ... 32. function animationLoop() { 33. // 1 - Clear 34. ctx.clearRect(0, 0, canvas.width, canvas.height); 35. // 2 - Draw 36. drawMonster(monsterX, monsterY, monsterAngle, 'green', 'yellow'); 37. // 3 - Move 38. if(mousePos !== undefined) { // test necessary, maybe the mouse is not yet on canvas 39. monsterX = mousePos.x; 40. monsterY = mousePos.y; 41. monsterAngle += incrementAngle; 42. } 43. ... 44. // call again mainloop after 16.6 ms (60 frames/s) 45. requestId = requestAnimationFrame(animationLoop); 46. }
This example shows one very important good practice when doing animation and interaction: if you want to achieve a smooth animation, set the state variables 60 times/s inside the animation loop (lines 45-49), depending on increments you set in event listeners (lines 23-31).
... <script> var canvas, ctx, previousMousePos; ... function drawLineImmediate(x1, y1, x2, y2) { // a line is a path with a single draw order // we need to do this in this example otherwise // at each mouse event we would draw the whole path // from the beginning. Remember that lines // normally are only usable in path mode ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } function handleMouseMove(evt) { var mousePos = getMousePos(canvas, evt); // Let's draw some lines that follow the mouse pos if (!started) { previousMousePos = mousePos; // get the current mouse position started = true; } else { // We need to have two consecutive mouse positions before drawing a line drawLineImmediate(previousMousePos.x, previousMousePos.y, mousePos.x, mousePos.y); previousMousePos = mousePos; } } window.onload = function () { ... started = false; canvas.addEventListener('mousemove', handleMouseMove, false); }; </script>
We had to define a variable started=false; as we cannot draw any line before the mouse moved (we need at least two consecutive positions).This is done in the test at line 21.
We just added mouseup and mousedown listeners, extract from the source code:
1. function handleMouseMove(evt) { 2. var mousePos = getMousePos(canvas, evt); 3. 4. // Let's draw some lines that follow the mouse pos 5. if (painting) { 6. drawLineImmediate(previousMousePos.x, previousMousePos.y, 7. mousePos.x, mousePos.y); 8. previousMousePos = mousePos; 9. } 10. } 11. 12. function clicked(evt) { 13. previousMousePos = getMousePos(canvas, evt); 14. painting = true; 15. } 16. 17. function released(evt) { 18. painting = false; 19. } 20. 21. window.onload = function () { 22. canvas = document.getElementById('myCanvas'); 23. ctx = canvas.getContext('2d'); 24. painting = false; 25. 26. canvas.addEventListener('mousemove', handleMouseMove, false); 27. canvas.addEventListener('mousedown', clicked); 28. canvas.addEventListener('mouseup', released); 29. };
Resizing a canvas can be tricky if we don’t know a few rules that might not be easily guessed:
Before looking at how best to handle canvas resizing, let’s see some examples below:
1. <script> 2. ... 3. function resizeCanvas() { 4. canvas.width = 300; 5. } 6. 7. </script> 8. ... 9. <button onclick="resizeCanvas();"> 10. Click this button to resize the canvas and erase it! 11. </button>
This time we are using a similar example as above, but we removed the button for resizing it, and we set the size of the canvas to 100x100 pixels. Instead of drawing inside, we draw two lines that join the diagonals.
Then, we added this CSS rule:
1. <style> 2. #myCanvas { 3. border: 1px solid black; 4. width:100% 5. } 6. </style>
And the result shows clearly that the resolution is still the same, only the pixels are bigger!
BEST PRACTICE: never use CSS percentages on a canvas width or height!
This is the trick to create a really responsive canvas:
Yep, this is not a straightforward process…
1. <div id="parentDiv"> 2. <canvas id="myCanvas" width="100" height="100" ></canvas> 3. </div>
<style> #parentDiv { width:100%; height:50%; margin-right: 10px; border: 1px solid red; } canvas { border: 2px solid black; } </style>
function init() { ... // IMPORTANT: there is NO WAY to listen to a DIV's resize // listen to the window instead. window.addEventListener('resize', resizeCanvasAccordingToParentSize, false); ... } function resizeCanvasAccordingToParentSize() { // adjust canvas size, take parent's size, this erases content canvas.width = divcanvas.clientWidth; canvas.height = divcanvas.clientHeight; ... // draw something, taking into account the new canvas size }
When the canvas is resized, its width became smaller than the monster’s size. We scale down the monster (using ctx.scale!) The code is very similar to the previous example, we just replaced drawDiagonals() by drawMonster(…), and we added a test in the drawMonster(…) function for scaling the monster if it’s bigger than the canvas width (look at lines 10-16), this is a common trick:
1. function drawMonster(x, y, angle, headColor, eyeColor) { 2. // GOOD PRACTICE: SAVE CONTEXT AND RESTORE IT AT THE END 3. ctx.save(); 4. 5. // Moves the coordinate system so that the monster is drawn 6. // at position (x, y) 7. ctx.translate(x, y); 8. ctx.rotate(angle); 9. 10. // Adjust the scale of the monster (200x200) if the canvas 11. // is too small 12. if(canvas.width < 200) { 13. var scaleX = canvas.width/200; 14. var scaleY = scaleX; 15. } 16. ctx.scale(scaleX, scaleY); 17. 18. // head 19. ctx.fillStyle=headColor; 20. ctx.fillRect(0,0,200,200); 21. ... 22. }
The canvas API is a “big beast”, and we have presented all the essential techniques for drawing and animating. However, we could not fit everything in this course. Exotic features that are rarely used by developers, or advanced techniques that require more than 20 lines of JavaScript, have been put aside for the W3Cx HTML5 Apps and Games course.
In that course, you will learn:
Congratulations! You made it to module 5. Welcome this time to the wonderful world of HTML5 forms. There have been no improvements since 1997 to HTML forms.
HTML5 this time comes with a set of new input fields for entering dates, colors, entering phone numbers, URLs, and so on. New attributes related to form validation have also been introduced, and they are related to a new build-in validation system that make things easier than before without the need to use JavaScript. However, JavaScript validation API is also available for people who would like to write their own customized validation system. These additions are very handy when used on mobile devices but raised many critics from Web designers, mainly related to their desktop implementations.
The course discusses all the cases and recommend solutions and good practices. Of course, like usual, many examples will be provided that you can run directly this time in the course pages, but also as standalone examples hosted on the JS Bin Web site. If you try to modify them or creates your own examples, please share in the discussion forums as usual. Have fun!
With HTML5, forms, which had shown little improvement since 1997, evolved considerably. To achieve this, Web developers relied on many popular JavaScript frameworks for validating input formats, providing various input GUIs, such as calendars for dates, sliders, etc. Frameworks such as jQueryUI, Dojo, and Sencha, all provide a widget set for improving forms. Furthermore, it was time to take into account the specifics of mobile web applications, where the GUI of a date chooser cannot be the same as a 400x400 pixel wide calendar on a desktop. Contextual virtual keyboards provided the way forward on smartphones and tablets thanks to Apple, Google and others.
Examples of contextual keyboards are shown above; they differ depending on the type of <input> fields in the <form>. In the examples, we can see: email, URL, and phone number. Look at the different keyboard layouts. The last picture is a date picker from an IOS phone.
There is a lot of course content covered this week, and before we get into all the details of the elements and attributes introduced by HTML5, we suggest playing with running examples of full featured forms.
This example was created by a learner (by Mels Le N.) from a previous version of this course. It uses the geolocation API presented in Module 6 for auto-filling the address input fields.
Feel free to look at the source code in the JS Bin:
Input elements, in particular the elements introduced by HTML5, can be used as widgets to control the behavior of a Web application. In this situation, they do not need to be inside a <form> element. We just bind event listeners to them and we use them as client-side widgets.
GUI: Graphical User Interface
The example used in the video is available online at JSBin. A screenshot of the resulted form is shown on the right.
Forms are a way to get user input which is sent to a remote server. This section of the course focuses on the HTML5 additions to forms, and as such will only cover the client-side part.
On the server side, you may have PHP, Java, C#, Ruby, Python, etc. components. There are several ways to collect server-side data from a form in a Web page: REST Web services, servlets, Microsoft ASP pages, etc.
On the client side, the forms indicate to which server and how the data should be sent, using the action and method attributes respectively. A <button type=“submit”> or an <input type=submit> field is used to submit the form content.
For example: <form action=“myServerCode.php” method=“POST”>…</form>. Here, we set the URL of the server side code (myServerCode.php), and the HTTP method that will be used by the browser for sending the form content (POST).
Another approach is to use JavaScript for sending the form content with Ajax. This is covered in W3Cx HTML5 Apps and Games course.
This week, let’s study the elements and attributes offered by HTML5, as well the HTML5 form validation API.
The example shown in the video shows some best practices for writing accessible forms and does some basic layout using CSS.
The following additional example shows the same best practices but presents a more complete form with CSS rules to make a nice layout. See it online, and illustrated with the screenshot below. It is adapted from this very good MDN’s article “How to structure a web form”.
Forms are commonly used to enable user interaction in Web sites and Web applications. For example, they are used for login, registering, commenting, and purchasing.
Since HTML5 provides functionalities to assist with accessibility, developers should make a concerted effort to mark up Web based forms. The following two guidelines are to give you a good start to make your forms accessible:
Examples for each of these two basic guidelines are given in the following pages.
The WAI Web site hosts a Forms tutorial where to find all guidelines to follow in order to make your forms truly accessible: labeling controls, grouping controls, form instructions, validating input, user notifications, multi-page forms, and custom controls.
Forms can be visually and cognitively complex and difficult to use. Accessible forms are easier to use for everyone, including people with disabilities.
Form fields and other form controls usually have visible labels, such as “E-mail Address:” as the label for a text field (see figure below).
When these labels are marked up correctly, people can interact with them using only the keyboard, using voice input, and using screen readers. Also, the label itself becomes clickable, which enables a person who has difficulty clicking on small radio buttons or checkboxes to click anywhere on the label text.
Whenever possible, use the label element to explicitly associate text with form elements. The for attribute of the label must exactly match the id of the form control.
Click on the label, not on the input field to see the effect.
1. <label for="first_name">Your First Name</label> 2. <input id="first_name" type="text" name="fname"/>
Note that you can also include the <input> element inside the <label>…<label> element, and also add a <span lang=“en”> for example, to indicate the language used in the label. Sometimes, nesting labels and inputs can also make CSS styling easier and produce better results with screen readers.
<label for=first_name"><span lang=en">Your First Name</span> <input id="first_name" type="text" name="fname"/> </label>
Click on the label “Subscribe to newsletter” to see what this does.
1. <label for="firstname">First name:</label> 2. <input type="text" name="firstname" id="firstname"><br> 3. 4. <label for="subscribe">Subscribe to newsletter</label> 5. <input type="checkbox" name="subscribe" id="subscribe">
The label of a <button> element is set inside the element and can include markup. This allows advanced accessibility hints to be included, such as marking up language change. Example: <button>Mon <span lang=“fr”>bouton</span></button>,for a button with a label in French.
When using the <input> element to create buttons, the label is set in the value attribute of the element. Example: <input type=“submit” value=“Please submit”>, renders a text button.
1. <button type="submit">Submit</button> 2. <button type="button">Cancel</button> 3. <input type="submit" value="Submit"> 4. <input type="button" value="Cancel">
These give the same results:
Lines 1 and 2 render as: Submit Cancel
… while lines 3 and 4 render as:
<label for="address">Enter your address:</label><br> <textarea id="address" name="addresstext"></textarea>
Groupings of form controls, typically groups of related checkboxes and radio buttons, sometimes require a higher level description. Grouping related form controls makes forms more understandable for all users, as related controls are easier to identify.
Grouping needs to be carried out visually and in the code, for example, by using the <fieldset> and <legend> elements to associate related form controls. The <fieldset> identifies the entire grouping and <legend> identifies the grouping’s descriptive text.
In the example below, there are three radio buttons that allow the user to choose an output format. Radio button groups should always be grouped using <fieldset>.
Output format:
<fieldset> <legend>Output format</legend> <div> <input type="radio" name="format" id="txt" value="txt" checked> <label for="txt">Text file</label> </div> <div> <input type="radio" name="format" id="csv" value="csv"> <label for="csv">CSV file</label> </div> [...] </fieldset>
In the example below, there are three checkboxes that are all part of an opt-in function for receiving different types of information.
I want to receive
<fieldset> <legend>I want to receive</legend> <div> <input type="checkbox" name="newsletter" id="check_1"> <label for="check_1">The weekly newsletter</label> </div> [...] </fieldset>
WAI-ARIA provides a grouping role that functions similarly to fieldset and legend. For example, a div element can have role=group to indicate that the contained elements are members of a group.
WAI-ARIA roles are very important in the accessibility world, and we invite you to see an example provided in the associated WAI tutorial. See also this MDN’s article about about WAI-ARIA roles.
In this section, we briefly present the input types, attributes, and elements related to the forms that came with HTML5. Details are given later, illustrated by multiple interactive examples.
Compared to HTML4, HTML5 introduced 13 new input types, covering most of the needs of Web developers. HTML5 packages some of the “form best practices” in its specification. Web browsers providing native implementation give a boost in performance, and reduce the size of JavaScript embedded in complex Web pages.
MDN’s Web docs article on <input> types lists all input types and highlights those that came with HTML5.
Now, let’s play with some of these input types and attributes.
For years, we used hundreds of lines of JavaScript for selecting colors. Now, it’s bundled in the browser!
Here is how it looks on some mobile devices:
<!DOCTYPE html> <html lang="en"><head>...</head> <body> Choose a color : <b><input type="color" value="#FF00F"/></b> </body> </html>Note: In this chapter we are simplifying the examples, as we usually embed input elements in a <form>…</form>.
Try <input type=“color”> online with this JSBin example. Or do it here in your browser: just click on the purple square below:
Here is the result on Google Chrome (works with other browsers too, though the look and feel may differ):
The <input type=“color”> can fire change or input events. Here is an example that changes the background color of the page when a color is chosen. Try it online at JSBin.
<!DOCTYPE html> <html lang="en"><head></head> <body> Select a color : <input type="color" id="colorChooser"/> <script> var colorInputField = document.querySelector("#colorChooser"); colorInputField.addEventListener('input', function(evt) { document.body.style.backgroundColor = this.value; }, false); </script> </body> </html>
By default, the color selector offers many options that may either frighten some users or just not be appropriate for the purpose of the application.
Good news: it is possible to restrict the choices, and also simplify the user interface, by using a <datalist> with some <option> elements inside.
Example: click the black rectangle on the right: . The following should be displayed:
<input type="color" value="#333333" list="colors"> <datalist id="colors"> <option>#0000FF</option> <option>#00FF00</option> <option>#FF0000</option> </datalist>
Note that the id of the <datalist> element should be the same as the value of the list attribute of the input field.
The main criticism that Web designers make about this element is related to its default appearance being strongly dependent on the browser and its underlying operating system. Changing the look and feel is not possible, except with the use of the options we saw in the previous sections of this page. This problem is also true for other input elements that renders as complex widgets, like <input type=“date”> and its variants.
Another problem is that there is no way to control where the dialog that contains the color chooser will appear - no positioning via CSS or JavaScript is possible. The specification does not say anything about how to position it over the page, thus the result is vendor specific.
The solution proposed by the W3C and its contributors is called Web Components, a new approach for designing HTML5 widgets, that is covered in the W3Cx HTML5 Apps and Games course.
For years, date and time pickers in HTML forms made Web developers rely heavily on JavaScript based widgets. The process is simpler in HTML5, which provides a special control to handle this specific kind of data natively.
Below are a few screenshots of the HTML5 date picker on several mobile devices. Note that the native date pickers of the operating systems are used:
The problem is different on a desktop. While it’s great to have native support for a date picker, Web developers would sometimes prefer 100% control over the look and feel of the date picker widget. For this purpose, the solution undoubtedly lies with the new Web Components (a way to make custom reusable widgets in HTML/CSS/JS), to be detailed in the W3Cx HTML5 Apps and Games course
Why don’t you try it yourself? Just click on this input field: (not in github)
With Firefox, it shows this date picker widget:
On non-supported browsers, it defaults to an <input type=“text”> input field.
The default usage is something like:
<label for="birthday">Choose birthday party date: </label> <input type="date" id="birthday">
Result: Choose birthday party date:
Most of the time you will add other attributes to give some restrictions (choose a date in the past, in the future, only on a Saturday, etc.).
The <input type=“date”> comes with several useful attributes. In particular the value, min and max attributes are used to propose a default date, a min and a max date, or for defining an interval of acceptable values.
Try this example: just click the next input field or try it online on JSBin if you want to tweak the source code:
... <input type="date" id="birthdayParty" value="2015-06-20" min="2015-06-20" max="2015-06-30"> ...
Using the value attribute for setting a date, and using step=7 for example, will make acceptable only the day of the week that corresponds to the value’s day (e.g.: only Mondays). Using step=2 will make acceptable only every other day, etc.
Example: we want to celebrate birthday parties only on Saturdays, check this on JSBin! (screenshot from Chrome).
<input type="date" id="birthdayParty" value="2015-06-20" min="2015-06-20" max="2015-06-30" step="7">
Online example at JSBin (screenshot from Chrome).
<input type="date" id="birthdayParty" list="birthdayPartyPossibleDates" value="2015-06-20"> <datalist id="birthdayPartyPossibleDates"> <option label="Best for me">2015-06-20</option> <option label="Ok for me too ">2015-06-27</option> <option label="This one is a sunday, hmmm">2015-06-28</option> </datalist>
The list attribute of the input element must match the id attribute of the datalist element.
If you use the min, max, or step attributes with a list attribute, it may filter the restricted list even more. Check this example on JSBin (tested with Google Chrome), that has a restricted list of three elements, one of which is filtered because it is not in in the min/max range.
Here is an interactive example at JSBin where you can change the type of date/time chooser. It also shows how to listen to the input event when a date/time is chosen.
1. <!DOCTYPE html> 2. <html lang="en"><head>...</head> 3. <body> 4. Testing the new date input field.<p> 5. Choose a date/time : <input type="date" id="date" /></p> 6. <p> 7. You picked: <span id="pickedDate"></span> 8. </p> 9. After you have tried the first example, change the value of the "type" attribute to: 10. <ul> 11. <li>datetime</li> 12. <li>datetime-local</li> 13. <li>time</li> 14. <li>week</li> 15. <li>month</li> 16. </ul> 17. And see the result. 18. <script> 19. var field = document.querySelector("#date"); 20. var result = document.querySelector("#pickedDate"); 21. field.oninput = function(evt) { 22. var date = this.value; 23. pickedDate.innerHTML = "<b>"+date+"</b>"; 24. } 25. </script> 26. </body> 27. </html>
Lines 20-26 show how we can detect a date change using JavaScript.
The object returned to the input event handler has a useful property named valueAsDate. This is a JavaScript date object that can be compared to other JavaScript date objects, in particular to the date of the day we can get with var date = new Date();
The following example at JSBin shows how to ascertain whether a date is in the past or in the future:
While if we enter a date in the future:
1. <body> 2. <label for="birthDate">Enter your birth date: </label><p> 3. <input type="date" id="birthDate" > 4. <p> 5. You picked: <span id="pickedDate"></span><p> 6. <span id="pastFuture"></span> 7. </p> 8. <script> 9. var field = document.querySelector("#birthDate"); 10. var result = document.querySelector("#pickedDate"); 11. var pastFuture = document.querySelector("#pastFuture"); 12. field.oninput = function(evt) { 13. var date = this.value; 14. pickedDate.innerHTML = "<b>"+date+"</b>"; 15. if(date.valueAsDate <= new Date()) { 16. pastFuture.style.color = 'green'; 17. pastFuture.innerHTML = "<b>Date in the past, ok!</b>" 18. } else { 19. pastFuture.style.color = 'red'; 20. pastFuture.innerHTML = "<b>Date in the future, you're not even born!</b>" 21. } 22. } 23. </script> 24. </body>
Lines 17-23 show how we can compare the date picked in the calendar widget with the current date. Note that we can compare any given dates using JavaScript. To check that the chosen date is before 2000 we would do this:
if(this.valueAsDate <= new Date(2000,1,1)) { ... } <input type="datetime">, "week", "month", "datetime-local", etc.
The HTML5 specification indicates that we can use <input type=“date”> and <input type=“time”> while for some years (before the specification became a frozen standard in October 2014), other variants were also present, such as type=datetime, datetime-local, month and week.
Here is an interactive example at JSBin where you can change the type of date chooser and try all the different possible values for the type attribute of date pickers.
<input type=“time”>
<input type=“datetime”>
<input type=“datetime-local”>
<input type=“week”>:
<input type=“month”>:
Let’s study 4 input types: email”, “tel”, “URL” and “search”.
<input type=“email”>
This input type is relatively straightforward to use. In mobile applications, this new input type pops up a keyboard layout adapted to email input. Note the “@” key, the “.” key, etc.
This input type is very interesting as it provides default validation behaviors:
If the value entered looks like an email address (contains a “@”…), the field is valid, and gets the pseudo CSS class :valid
If the value entered does not contain an “@”, and does not look like an email address, the field is invalid and gets the pseudo CSS class :invalid
See the next example to see this in action. More details will be presented in a later section dedicated to form validation.
Typical use: Online example at CodePen
Try it on your browser:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of input type=email</title> 6. <style> 7. input:invalid { 8. background-color:pink; 9. } 10. </style> 11. </head> 12. <body> 13. <label for="email">Enter your email </label> 14. <input type="email" id="email"> 15. </body> 16. </html>
Note the CSS rule that turns the background color of the email input field to pink if a user enters an invalid address (lines 7-8). Also note that the validation is based only on matching a regular expression (the address should contain a “@”, a “.”, etc.). It does not check if the address is an existing one.
This input field is really useful on smartphones and tablets, as it makes the browser pop up a keyboard layout suitable for entering phone numbers:
This input type is often used with the new placeholder and pattern attributes that are detailed in another section of this course. It is supported by all recent major Web browsers, on mobile devices and desktops.
Try it in your browser (we used the same CSS for changing the background- color when the input value is invalid):
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of input type=tel</title> 6. <style> 7. input:invalid { 8. background-color:pink; 9. } 10. </style> 11. </head> 12. <body> 13. <label for="tel">Enter a telephone number:</label> 14. <input type="tel" id="tel" 15. placeholder="(555) 555-5555" 16. pattern="(?d{3})?[-s]d{3}[-s]d{4}.*?)"/> 17. </body> 18. </html>
This input field is really useful on smartphones and tablets, as it makes the browser pop up a keyboard layout suitable for entering URLs:
This field is also compatible with the validation API (more on this in another section).
Here is an online example that shows the use of the placeholder and pattern attributes
for entering only URLs that start with ftp:// or https://
Or try it here in your browser:
Enter a URL (default validation):
Enter a URL (custom validation, must start with http, https or ftp):
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head > 4. <meta charset="utf-8"> 5. <title>Example of input type=url</title> 6. <style> 7. input:invalid { 8. background-color: lightPink; 9. } 10. </style> 11. </head> 12. <body> 13. <label for="url1">Enter a URL (default validation):</label> 14. <input type="url" id="url1"/> 15. <p> 16. <label for="url2">Enter a URL (custom validation, must start with http, https or ftp):</label> 17. <input id="url2" type="url" placeholder="https://www.domain.com" 18. pattern="(http|https|ftp)://[a-zA-Z0-9-./]*"/><p> 19. </body> 20. </html>
Lines 16-17 show the use of a pattern attribute with a JavaScript regexp that accepts only URLs starting with http, https or ftp. More details on the pattern attribute are given in the section that presents the new HTML5 form attributes.
The search type is used for search fields (i.e., for a search
engine). A search field behaves like a regular text field, except that
it may provide some feedback GUI for stopping the current request and
emptying the search field, or it may provide a drop-down list of recent
search results.
The specification does not state what the GUI should look like, so
current implementations show variations in the look and feel.
<label for="search1">Simple search: </label> <input type=search id="search1"> <p> <label for="search2">Search with attribute <code>results=5</code> (try with Safari): </label> <input type=search id="search2" results=5>
Results on Chrome and Opera desktop - notice the small cross on the right when one enters a value:
Same example with Safari desktop, this time the second line with an attribute results=5 shows a small icon on the left:
Example that shows a drop down list of recent searches (Safari screenshot borrowed from this excellent site about HTML5 forms that is worth reading):
1. <!DOCTYPE html> 2. <html> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of input type=email</title> 6. <style> 7. input:? { 8. background-color:pink; 9. } 10. input:?? { 11. background-color:lightGreen; 12. } 13. </style> 14. </head> 15. <body> 16. <label for="email">Enter your email:</label> 17. <input type="email" id="email"> 18. </body> 19. </html>
This input field is useful for entering numerical values (integer or float), but not for entering zip codes. On desktop implementations and on some mobile implementations, it provides a user interface with small vertical arrows for incrementing/decrementing the current value, while on mobiles it will display a numeric keyboard.
For zip codes, a <input type=“text” pattern=“……”> is preferable. See examples given in the pattern attribute section of this course.
Example: <input type=“number” value=“25” min=“0” step=“5” max=“500”/>
Examples on desktop (the width will be adjusted depending on the min and max attributes):
<input type="number" value="25" min="0" step="5" max="500"/>
This field accepts specific attributes max, min, step, value (default displayed value).
This input type is very interesting as it provides default validation behaviors:
If the value entered using a keyboard is not a valid number, or is not in the range defined by the min and max attributes, the field is invalid and gets the pseudo CSS class :invalid.
If the difference between the value you enter and min is a multiple of step, then it gets the CSS pseudo class :valid , otherwise it will be invalid. Example: if min=1 and step=5, the field will be valid with value=1, 6, 11, 16 etc. if min=0, with value=0, 5, 10, 15 etc.
WARNING 1: Using a step attribute with an integer value will make the arrows increment/decrement the current value with the step value, and [make the input field valid only when ]the difference between the value you enter and min is a multiple of step.
WARNING 2: by default, omitting the step attribute is equivalent to step=“1”, [so for entering float values, it is necessary to use step=“any” or step equal to a floating point value such as step=“0.1”].
With step=“any”, floating point values are valid, but vertical arrows will increment/decrement the value by one. If step=“0.1”, arrows will increment/decrement by 0.1, etc.
Online example in CodePen try changing the attribute values, use step=“any” and try float values, etc).
Or, do it here in your browser (Manually enter a value that is not in the range, or not a multiple of 5, try the up and down arrows, etc.):
Quantity (between 0 and 500, should be a multiple of 5 otherwise it’s invalid):
1. <!DOCTYPE html> 2. .... 3. <style> 4. #number:invalid { 5. background-color:pink; 6. } 7. #number:valid { 8. background-color:lightGreen; 9. } 10. </style> 11. </head> 12. <body> 13. Example of <code><input type=number></code>:<p> 14. <label for="number">Quantity (between 0 and 500, should be a multiple of 5 otherwise it's invalid): </label> 15. <input type="number" id="number" value="25" min="0" step="5" max="500"/> 16. <p> 17. Change the different values for attributes step, max, min, value. Don't forget to try step="any" for float values... 18. </body> 19. </html>
This input type renders as a slider. It accepts the same attributes as the <input type=“number”> : min, max, step and value.
The basic use is to specify at least the value, min and max attributes, and eventually the step attribute, too:
<input id="slider6" type="range" min="0" max="10" step="2" value="5">
But most of the time, you will need a visual feedback that shows the current value selected by the slider.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of input type=tel</title> 6. <style> 7. #rangeValue1 { 8. border:1px solid black; 9. padding:2px; 10. } 11. </style> 12. <script> 13. window.onload = function() { 14. // Called when the page is loaded, for displaying initial value in the output 15. printValue('slider1','rangeValue1'); 16. } 17. function printValue(sliderId, outputId) { 18. var x = document.getElementById(outputId); 19. var y = document.getElementById(sliderId); 20. x.value = y.value; 21. } 22. </script> 23. </head> 24. <body> 25. <form > 26. <label for="slider1">Select a value:</label> 27. <input id="slider1" type="range" 28. min="100" max="500" step="10" value="150" 29. oninput="printValue('slider1','rangeValue1')"/> 30. <output id="rangeValue1"></output> 31. </form> 32. <br/> 33. Play with attributes: value, min, max, step... 34. </body> 35. </html>
When you click and drag the slider, it “jumps” to some snap points corresponding to the integer values of the range defined by the min and max attributes. The “size of the jumps” depends on the value of the step attribute.
Try these examples in your browser and look at their behavior:
value=5 min=0, max=10 step=1: value=12 min=10, max=50 step=4:
Note that in the previous example, the default value displayed is 14, not 12 (the value just above min plus an integer step value). 12 is not possible so it’s been “snapped” to 14.
value=5 min=0, max=10 step="0.5":
In the previous example, it’s necessary to add quotes for setting step=“0.5” (while HTML5 authorizes not using quotes for setting integer values to attributes).
value=5 min=0, max=10 step="any":
WARNING: Using a step attribute with an integer value will make the slider jump corresponding to the step value. By default, omitting the step attribute is equivalent to step=“1”.
For accepting float values, it is necessary to use step=“any”, or step equal to a floating point value, such as step=“0.5”.
Adding “ticks” to the range slider using a <datalist> element
1. <label for="slider2">value=5 min=0, max=10 step=1, ticks at 2, 4, 6, 8 and 10:</label> 2. <input id="slider2" type="range" 3. list="ticks2" 4. min="0" max="10" step="1" value="5"/> 5. <datalist id=ticks2> 6. <option>0</option> 7. <option>2</option> 8. <option>4</option> 9. <option>6</option> 10. <option>8</option> 11. <option>10</option> 12. </datalist>
value=5 min=0, max=10 step=1, ticks at 2, 4, 6, 8 and 10: value=20 min=10, max=50 step=5, ticks at 0, 10, 20, 30, 40 and 50: value=5 min=0, max=10 step="0.5", ticks at 0, 0.5, 1, 2, 4, 8: value=5 min=0, max=10 step="any", ticks at 0, 5 and 10:
- name | - form |
- disabled* | - readonly |
- type | - autocomplete |
- maxlength | - autofocus |
- readonly | - list |
- size | - pattern |
- value | - required* |
- alt | - placeholder |
- src | - multiple |
- height | - list |
- width | - min |
- checked* | - max |
- align | - step |
- formaction | |
- formenctype | |
- formmethod | |
- formtarget | |
- formnovalidate | |
* pseudoclasses CSS target with :disabled and :checked or :required selectors | |
** align is deprecated, CSS rules should be used instead |
In this chapter, we go over the form attributes that have been introduced by HTML5.
We have already seen the use of pseudo CSS classes used together with the input field and form validation (pattern attribute, input:invalid CSS rule). We also briefly looked at the use of the placeholder attributes for displaying a helper message in the input field.
In this section, we cover the rest of the form attributes and provide further examples of using the previously discussed attributes.
In another part of the course, about form validation and visual feedback using CSS, we examine some of the most useful attributes in even greater detail.
This attribute is useful for putting input fields outside the form itself. The form attribute of an external input field must share the same value as the id of the form the field belongs to. This is useful when using <fieldset> elements for making the page/form layout easier.
Try this interactive example in CodePen, or try it directly in your browser:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of input type=tel</title> 6. 7. </head> 8. <body> 9. <label for="yourName">Enter your name:</label> 10. <input type="text" id="yourName" name="yourName" form="form1"/> 11. <p> 12. <form id="form1" action="sumit.php" method="post"> 13. <fieldset> 14. <legend>Choose option</legend> 15. <label for="free">Free registering</label> 16. <input type="checkbox" id="free"/> 17. <label for="premium">Premium</label> 18. <input type="checkbox" id="premium"/> 19. 20. <button type="submit">Send form</button> 21. </fieldset> 22. </form> 23. </body> 24. </html>
Lines 12 and 22 shows the form attribute. Make sure that its value matches the id of the form!
This attribute applies either to the <form> element or on individual <input> elements. It specifies when input fields must autocomplete the user’s input based on the user’s typing history.
Possible values of this attribute: on/off.
If applied to the <form> element, all input fields attached to the form (inside or linked to it using the form attribute), will have auto-completion set by default to the value of the autocomplete attribute of the form.
This default behavior can be overridden by setting it individually to any input field inside. In other words: it is possible to have autocomplete “on” for the form, and “off” for specific input fields, or vice-versa.
Sometimes this autocomplete behavior is disabled by default in some Web browsers, and will need to be adjusted in the preferences/settings.
This attribute targets most input types (those that allow typing in them).
Try it in your browser here:
1. <form submit="test.php" method="post" autocomplete="on"> 2. ... 3. <label for="address">Enter your address (autocomplete off, overrides the 4. form's autocomplete=on attribute):</label> 5. <input type="text" id="address" autocomplete="off"> 6. <p> 7. <label for="address1">Enter your address (autocomplete on by inheritance of 8. the form's autocomplete=on attribute):</label> 9. <input type="text" id="address1"> 10. <p> 11. <button type="submit">Submit</button> 12. ... 13. </form>
This attribute is useful for transferring the focus to a field other than the first field in a page/form (by default the first input field has the focus).
Attention: there must not be more than one element in the document with the autofocus attribute specified!
This example below illustrates the use of the autofocus attribute: the focus is put on the second input field of the page. It also shows the use of required, placeholder and pattern attributes.
The required attribute makes the input field invalid if kept empty.
1. <form> 2. ... 3. <input type="text" id="test"/><p> 4. ... 5. <input id="name" name="inputName" 6. placeholder="6 to 9 chars please..." 7. pattern="w{6,9}" 8. required 9. autofocus 10. type="text"/> 11. ... 12. </form>
Important: : For “Boolean” attributes, such as autofocus, required, optional, etc., you are able to either write autofocus=“autofocus”, or just use the attribute name “autofocus” without setting a value.
Read these explanations for a complete description of the syntax of Boolean attributes.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head><meta charset="utf-8"> 4. <title>Example for a knowledge check</title> 5. </head> 6. <body> 7. <form> 8. <label for="studentID">Student ID (disabled field, cannot type in it): </label> 9. <input type="text" value="S134356" id="studentID" disabled/><p> 10. 11. <label for="name">First name: </label> 12. <input type="text" id="firstName" 13. placeholder="John" 14. autofocus 15. /> 16. 17. <label for="lastName">Last name: </label> 18. <input type="text" id="lastName" 19. placeholder="Smith" 20. autofocus 21. /> 22. </form> 23. </body> 24. </html>
This attribute works together with the new <datalist> element we already studied when we saw the color and date input fields.
This attribute’s value must match the id of a <datalist> element. It is useful for providing local auto-completion to some input fields, or for restricting the possible values on some others like <input type=date> or <input type=color>.
Here is a small code extract from a more complete example shown in the section about the new <datalist> element (see next unit).
Please try it in your browser (Type “F”, “E”, “O”, C” etc., or just click inside the field and use the drop down menu). Note that you can also enter any value; if it does not start with one of these letters it will be accepted but will not trigger auto-completion.
1. <form> 2. ... 3. <input list="browsers" id="mybrowser" /> 4. 5. <datalist id="browsers"> 6. <option value="Internet Explorer"> 7. <option value="Firefox"> 8. <option value="Chrome"> 9. <option value="Opera"> 10. <option value="Safari"> 11. </datalist> 12. ... 13. </form>
At lines 3 and 5, the value of the list attribute of the input field must match the one of the id of the <datalist> element.
The pattern attribute enables the validation of the user’s input on the fly (also at submission time), based on regular expressions. It applies to the text, search, url, tel, email, and password input types.
The pattern attribute follows the syntax of JavaScript regular expressions.
A must read: a good catalog of ready-to-go patterns is available at html5pattern.com, an excellent Web site that proposes plenty of JavaScript patterns for the pattern attribute of HTML5 forms. The left hand menu proposes categorized patterns for postal codes, dates, phones, etc.
You can also try this online JavaScript RegExps tester, and follow this tutorial about “using JavaScript RegExps” that has step by step exercises and explanations.
Just add a pattern attribute with a value that is the JavaScript regular expression that must match the entire string entered in the field. Note that the empty string is valid by default (except if the required attribute is used - this makes empty fields invalid).
It’s best practice to systematically add a title attribute with a value that indicates what constitutes a valid entry. More on this in the section of this course dedicated to form validation.
1. <input type="text" name="country_code" 2. pattern="[A-Za-z]{3}" 3. title="3 letter country code" 4. />
Try this online example at JSBin or directly in your browser below:
With the previous example, until the value of the input field is equal to 3 alphabetic characters, the field is invalid.
As seen in the previous examples, we used some CSS pseudo classes for automatically setting the background-color of the input field as we type.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of the pattern attribute</title> 6. <style> 7. input:invalid { 8. background-color: lightPink; 9. } 10. input:valid { 11. background-color: lightGreen; 12. } 13. </style> 14. </head> 15. <body> 16. <label for="code">Please enter a 3 letter country code:</label> 17. <input type="text" name="country_code" 18. pattern="[A-Za-z]{3}" 19. title="3 letter country code" 20. id="code"/> 21. </body> 22. </html>
Try this example online or in your browser below:
Attributes used: placeholder (for displaying a ghost example value), pattern, required (empty field = invalid)…
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> <meta charset="utf-8"> 4. <title>Example of use of new HTML5 input field attributes</title> 5. <style> 6. input:focus:invalid { background-color: lightPink;} 7. input:valid { background-color:lightGreen; } 8. input:required {border: 2px solid red; } 9. input:optional {border: 2px solid green; } 10. </style> 11. </head> 12. <body> 13. <p>Attributes used: placeholder (for displaying a ghost example value), pattern, required (empty = invalid)... 14. <p> 15. <label for="inputID">Enter a pseudo (6-12 characters): </label> 16. <input id="inputID" name="Name" 17. placeholder="Name" 18. pattern="w{6,12}" 19. required 20. title="6-12 characters allowed please" 21. type="text" /> 22. </body> 23. </html>
Online example at JSBin try it in your browser:
1. <input 2. id="website" 3. name="url" 4. type="url" 5. placeholder="http://www.domain.com" 6. title="http, https or ftp allowed" 7. pattern="(http|https|ftp)://[a-zA-Z0-9-./]" 8. />
These attributes are useful for several input types such as number, range, date and time (and other variants).
The min and max attributes are used to set ranges to input fields that accept numerical values or a date/time.
Their detailed use with these input fields have already been explained in section 5.4 of this course dedicated to these particular input field types.
1. <input id="mydate" name="mydate" 2. type="date" 3. min="2012-01-01" 4. max="2013-01-01" 5. value="2012-01-01" 6. /> 7. 8. <input name="time" id="time" type="time" 9. min="09:00" 10. max="17:00" 11. value="12:00" 12. /> 13. 14. <input id="range" name="range" type="range" min="0" max="100" step="5"/>
The multiple attribute is used with email and file input types. It’s a Boolean attribute, so here are the different syntax possibilities:
With <input type=“email”>
With the <input type=“email”>, this attribute enables the user to enter a set of addresses, separated by a comma instead of a single address. Entering several addresses will keep the input field valid.
Or try it below in your browser: type in a list of email addresses separated by a comma, then look at the input field background color (pink = invalid, green = valid), and then submit:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Jsbin</title> 6. <style> 7. input:invalid { 8. background-color: lightPink; 9. } 10. input:valid { 11. background-color: lightGreen; 12. } 13. fieldset { 14. border:1px solid; 15. padding:20px; 16. } 17. </style> 18. </head> 19. 20. <body> 21. <p>This form uses: <code><input type="email" name="myemail" <b>multiple</b>></code></p> 22. 23. <form> 24. <fieldset> 25. <legend>With the multiple attribute </legend> 26. <label>Enter several email addresses: </label> 27. <input type="email" name="myemail" title="you can enter multiple emails addresses, separated by a comma" multiple/> 28. <button>Submit</button> 29. </fieldset> 30. </form> 31. <p> 32. <p>This form does not use the multiple attribute:</p> 33. 34. <form> 35. <fieldset> 36. <legend>Without the multiple attribute </legend> 37. <label>Enter several email addresses: </label> 38. <input type="email" name="myemail" title="only one address please!"/> 39. <button>Submit</button> 40. </fieldset> 41. </form> 42. <p> 43. Type in a list of email addresses separated by a comma. Look at the input field background color (pink = invalid, green = valid), try to submit. </p> 44. </body> 45. </html>
Best practice: add a title attribute indicating what you expect as a valid entry (lines 25 and 38). If you enter bad values and submit, you will see in the error message the string value of the title attribute.
With <input type=“file”>
With this type of input field, multiple files can be chosen (whereas before HTML5, only a single file could be chosen).
Typical use: <input type=file multiple>
Try these in your browser, look at the small variations (text in the buttons, messages):
Example with <input type=file multiple>
Select one or more files:
Example without the multiple attribute:
Select only one file:
Use the standard key modifiers (shift, control, command) for selecting multiple files when the file chooser dialog popup.
In the following pages, we present a set of rarely used attributes introduced by HTML5.
You might just glance at them and/or try the examples. The next pages cover their usage and you are welcome to use them for future reference (for those of you who like to cover the topics completely).
These attributes are targeted to the <input type=“submit”> input fields. They are rarely used.
1. <input type="submit" 2. formaction="preview.php" formmethod="get" value="Preview">
When you use an <input type=“submit”> field with the formaction attribute, the action attribute value of the form is overridden. The form will be submitted to the URL / value of the formaction attribute of the <input type=“submit”> field.
The formmethod attribute does the same with the POST/GET method attribute of the form. If an <input type=“submit”> has a formmethod attribute, it overrides the value of the method attribute of the form.
1. <form action="post.php" method="post"> 2. <input type="submit" 3. formaction="preview.php" formmethod="get" 4. value="Preview"> 5. <input type="submit" value="Send"> 6. </form>
Line 3 overrides the values set in line 1.
Here are two online examples at JSBin:
The first shows a form with two submit buttons:
The second example shows a form with two submit buttons:
The formnovalidate attribute is targeted to the <input type=“submit”> input fields. This attribute is rarely used.
This atrribute allows the submission of a form even if it contains invalid fields. For example: a form that has an <input type=“email”> field or a field required and which are not filled.
In general, such forms have two submit buttons, one with the formnovalidate attribute set to a non null value and one without.
Typical use (online example at JSBin):
1. <form action="form.php"> 2. <fieldset> 3. <legend>Example of formnovalidate attribute</legend> 4. <label for="email">E-mail:</label> 5. <input type="email" name="email" id="email"/><br> 6. <input type="submit" value="Submit" /><br> 7. <input type="submit" 8. formnovalidate 9. value="Submit without validation" /> 10. </fieldset> 11. </form>
The formtarget attribute is targeted to the <input type=“submit”> input fields. This attribute is rarely used.
This attribute’s value indicates where the response from the form submission should be displayed.
1. <input type="submit" 2. formtarget="_blank" 3. value="Submit but show results in a new window/tab">
Online example at JSBin or try it in your browser below:
1. <form action="defaultAction.php"> 2. <label for="givenName">Given name:</label> 3. <input type="text" name="givenName" id="givenName"><br> 4. <label for="familyName">Family name:</label> 5. <input type="text" name="familyName" id="familyName"><br> 6. <input type="submit" value="Submit as usual"> 7. <input type="submit" 8. formtarget="_blank" 9. value="Submit but show results in a new window/tab"> 10. </form>
A word about the enctype attribute of the <form> element
The enctype attribute existed before HTML5. It is often used together with forms that contain file input fields. For sending files to a remote server, we use “multipart” forms. This special encoding of forms needs to be specified using the enctype attribute, as shown in the example below:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Jsbin</title> 6. </head> 7. <body> 8. <form action="default.php" method="post" enctype="multipart/form-data"> 9. Given name: <input type="text" name="gname"><br> 10. Family name: <input type="text" name="fname"><br> 11. <input type="submit" value="Submit"> 12. </form> 13. </body> 14. </html>
Note that when you send form content using Ajax, this attribute is not needed, as you will specify the type of data sent to the remote server in JavaScript, using the FormData object.
Since HTML5, this attribute can also be used in <input type=“submit”> input fields.
If an <input type=“submit”> field has this attribute, then, when submitted using method=POST, the browser will send the form content encoded with the method specified by the formenctype attribute. And this overrides the value of the enctype attribute specified in the <form enctype=…> element (or its default value, if not present).
<form action="defaultAction.php"> ... <input type="submit" formenctype="multipart/form-data" value="Submit as Multipart/form-data"> </form>
Try this online example at JSBin
Source code:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Jsbin</title> 6. </head> 7. <body> 8. <form action="defaultAction.php" method="post" 9. enctype="application/x-www-form-urlencoded"> 10. <label for="givenName">Given name:</label> 11. <input type="text" name="givenName" id="givenName"><br> 12. <label for="familyName">Family name:</label> 13. <input type="text" name="familyName" id="familyName"><br> 14. <input type="submit" value="Submit"> 15. <input type="submit" 16. formenctype="multipart/form-data" 17. value="Submit as Multipart/form-data"> 18. </form> 19. <p><b>Note:</b> The formenctype attribute is not supported by all browsers.</p> 20. </body> 21. </html>
If you run this example in the JSBin standalone mode (click the black arrow on the top right of the output tab, in JSBin), you should see this:
Then, open the devtools and go to the “Network” tab, click on the POST request. Once done, click on the right on the “Header” tab to see the HTTP headers, and scroll down, you should see the form-data entries in the header, like in this screenshot:
And if you start again and click on the left submit button, the one without the formenctype attribute, you should see that the form content has been submitted “normally” (default value is “urlencoded”, spaces are replaced by “+”, etc.). Here is a screenshot of what you should see:
Let’s look at the HTML5 elements related to forms (specifically: <datalist>, <output>, <meter> and <progress> elements).
HTML4 | HTML5 |
---|---|
- <form> | - <datalist> |
- <fieldset> | - <output> |
- <legend> | - <meter> |
- <textarea> | - <progress> |
- <label> | - <keygen> * |
- <select> | |
- <option> | |
- <optgroup> | |
- <input> | |
- <button> | |
* Not really useful for most developers. |
The output element represents the result of a computation or user action. You can see it as a “specialized <div> or <span>” for displaying interactive results.
Do not hesitate to play with the source code of these examples online at JSBin.
1. <form oninput="o.value=a.valueb.value"> 2. <input type="number" name="a" id="a" value="2"> x 3. <input type="number" name="b" id="b" value="3"> = 4. <output for="a b" name="o">6</output> 5. </form>
The oninput event handler directly uses the <output> element using the value of its name attribute.
Result (do change the input field values):
Top of Form
50 + =
Bottom of Form
1. <form > 2. <input name="a" value="50" type="range" 3. oninput="x.value = a.valueAsNumber + b.valueAsNumber; 4. y.value = this.value;"/> 5. <output id="y">50</output> + 6. <input name="b" value="50" type="number" /> = 7. <output name="x" id="x" for="a b"></output> 8. </form>
HTML5 has introduced new input field properties: valueAsNumber and valueAsDate.The last example is similar to the previous one except that we use an addition instead of a multiplication.
As input field values are considered as strings by JavaScript, using x.value = a.value + b.value would result in a string concatenation instead of an addition. That’s why we use the valueAsNumber property.
This is why we used the valueAsNumber property also introduced by HTML5 for some input fields such as <input type=“range”> and <input type=“number”>, we also encountered the valueAsDate properties when we studied <input type=“date”>.
The <meter> element displays colored bars to represent numeric values.
It can be useful to display a colored gauge to show disk usage, to highlight the relevance of a query result, or the fraction of a voting population that favours a particular candidate, etc. This element is often used with the <input type=“range”> field as an instant feedback indicator.
The <meter> element should not be used to indicate progress. You should instead use a <progress> element.
Storage space used: <meter value=75 min=0 low=20 high=80 max=100 optimum=50></meter>
The <meter> element uses the easy-to-understand value, min, max,
low, high and optimum attributes.
The optimum attribute, along with min, low, high and max attributes will
affect the color of the bar, and of course the constraint min < low <
high < max should be respected.
More explanations about the colors and the meaning of the optimum attribute will come further in this lesson.
Try the next example online at JSBin or just play with it in your browser by dragging the slider below:
<meter value=75 min=0 low=20 high=80 max=100 optimum=19></meter>
1. <p>Grades: <meter id="meter2" value="75" min="0" low="20" high="80" max="100"></meter> 2. 3. <input min="0" max="100" value="75" id="meter2range" 4. oninput="effect('meter2', 'meter2range')" type="range"> 5. <output id="meter2val" for="meter2range"></output></p> 6. <script> 7. function effect(meter, meterrange) { 8. var currVal = document.getElementById(meterrange).value; 9. document.getElementById(meter).value = currVal; 10. document.getElementById(meter+ "val").innerHTML = currVal; 11. } 12. </script>
The link between the slider (an <input type=range>) and the meter element is done using an input event handler (oninput=“effect(…)”) at line 4.
The effect JavaScript function will change the current value of the <meter> element (line 9) and update the displayed html content of the <output> element (line 10).
The color of the gauge changes depending on the attribute’s values
The optimum attribute indicates the optimal numeric value and gives an indication where along the range is considered preferable. Just think of the <meter> ranges as follows:
… and depending on the value you set to optimum attribute, one of the ranges above becomes the “good (optimum)” range.
In the previous example, with the value of the optimum attribute set to 19, a number between min and low (not inclusive), the Range 1 (between min=0 and low=20) becomes the “good (optimum)” range (displayed in green), the Range 3 (between high=80 and max=100) becomes the “bad” (displayed in red color) range, and the Range 2, in the middle, will be displayed in yellow (not optimum, not bad).
A <meter> element used for displaying blood pressure might be a good candidate for setting the optimum value to “Range 2”, and a <meter> element used for displaying memory usage might be a good candidate for setting the optimum value to “Range 1”, meaning that a low memory usage is “good”.
The <progress> element is similar to <meter> but it is used for progress bars (i.e., the percentage of a file being uploaded, etc.):
<progress id=pr value=50 min=0 max=100>
The browser calculates the percentage corresponding to the value, min and max attributes and adjusts the length of the progress bar accordingly.
If no value attribute is set, the progress bar will display an “indeterminate look”, that may slightly vary among different browser implementations.
Here is an online example at JSBin, or try it below in your browser:
This example uses some JavaScript to simulate a download progress by changing in real time the value attribute.
The progress below is defined like this:
<progress id=pr value=100 max=1000>
1. Download progress: <progress id=pr value=100 min=0 max=1000></progress> 2. <script> 3. var i=0; 4. setInterval(function () { 5. i = (i+1) %1000; 6. document.getElementById(pr).value = i; 7. },1); 8. </script>
The <datalist> form element is useful for linking a list of choices to an input element.
We have already seen this element in action with different <input> elements, such as <input type=“color”>, <input type=“date”>, or <input type=“range”>.
It is often “linked” to input fields either for restricting the value set that can be proposed (i.e., restricted set of colors or possible dates, or for displaying slider ticks, as shown above), but it may also be used in a more general way, for providing client-side auto-completion without the need to use JavaScript.
It works with the new list attribute of input fields introduced by HTML5. The id of the <datalist> must match the value of the list attribute in the input field. A datalist can be shared by several input fields. It suffices that their list attribute matches the id of the datalist element.
The input field is related to the datalist that will propose auto-completion based on <datalist> values.
Here is an online example at JSBin, or try it here in your browser (type the name of your favorite browser):
1. <form action="demo_form.asp" method="get"> 2. <input list="browsers" name="browser" /> 3. 4. <datalist id="browsers"> 5. <option value="Internet Explorer"> 6. <option value="Firefox"> 7. <option value="Chrome"> 8. <option value="Opera"> 9. <option value="Safari"> 10. </datalist> 11. <input type="submit" /> 12. </form>
As you can see at lines 2 and 4, the id and list attributes match. The <datalist> element is wrapped around a set of <option> that are available for selection by another form control (in this example the input field from line 2).
In this section of the course, we will look at CSS pseudo classes that are useful for giving instant feedback when the user’s input is not valid. We will also look at the new JavaScript API introduced by HTML5 for validating forms and form elements.
In the following pages, we will first illustrate the concept of form validation with the <input type=“email”/> field. It can be generalized to all kind of input types, such as url, number, etc. Some form attributes, such as pattern, will also affect input field validity!
Form validation is supported by all modern browsers.
Most modern browsers propose default behavior for validating input fields and forms.
The built-in validation system that comes with HTML5 automatically adds a CSS pseudo class to all input fields. Invalid fields (i.e. a badly worded email address in an <input type=“email”> input field), will inherit the :invalid pseudo class, valid fields will inherit the :valid pseudo class.
A first step to improve your HTML form is to add some CSS rules to your input fields. This adds visual feedback to the validity of input fields values - while the user is typing - such as changing the color of the border of input fields, or green/red icons on the right of the field, as shown in the small picture at the top right of this page.
Also, at the time of submitting the form, some extra messages may be displayed as pop up text bubbles.
The default bubble message and visual feedback differ from one implementation to another, but they may be customized, with some limitations that will be explained later.
For example, browsers may provides default feedback on the input field’s border (red = invalid, green = ok). This default behavior can be overridden by CSS rules as illustrated in the section about new input type attributes.
Here is an online example at JSBin, or try it below in your browser:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>CSS3 pseudo-classes for form validation visual feedback</title> 6. <style> 7. 8. input:invalid { background-color: lightPink;} 9. input:valid { background-color:lightGreen; } 10. input:required {border: 2px solid red;} 11. input:optional {border: 2px solid green;} 12. fieldset { 13. border:1px solid; 14. padding:20px; 15. } 16. .formLabel { display: inline-block; width: 140px; text-align: right; } 17. </style> 18. </head> 19. <body> 20. 21. <form> 22. <fieldset> 23. <legend>Type invalid values and see the result</legend> <label for="myEmail" class="formLabel">E-mail:</label> <input type="email" id="myEmail" required/><br> <label for="myURL" class="formLabel"> Homepage (URL):</label> <input type="url" id="myURL" required/><br> <label for="myPhone" class="formLabel"> Phone number:</label> <input type="tel" id="myPhone" pattern="[0-9]{3}-?[0-9]{3}-?[0-9]{4}" placeholder="e.g. 416-555-1234" required/><br> 24. <button>Submit form</button><br /> 25. </fieldset> 26. </form> 27. <p> 28. 29. </body> 30. </html>
Try the online example with different Web browsers, both with and without the CSS rules. See the differences between FireFox/Chrome/Opera in the default visual feedback behavior. Don’t worry: all default behavior can be overridden if you provide your own CSS rules.
Best practice: We recommend that you ALWAYS provide default CSS rules that give visual feedback to the user’s input.
Try this online example at JSBin or try it here in your browser. This example adds a small icon that changes depending on the validity of the input field:
1. .myForm input:focus { 2. padding-right:70px; 3. } 4. .myForm input { 5. transition: padding .25s; 6. } 7. 8. .myForm input:required:valid { 9. background:url(https://i.imgur.com/BJolppS.png) no-repeat right top; 10. } 11. .myForm input:required { 12. background:url(https://i.imgur.com/7pIN7wz.png) no-repeat right top; 13. }
This time, we just added an attribute class=“myForm” to our form, in order to avoid interfering with the other examples on this page, and we tweaked the CSS rules a little.
The rule at line 1 says that any time we click on an input field, it will enlarge itself to the right, while the rule at line 4 will make it animated.
The rules at lines 8 and 11 target the input fields with a required attribute. They will change the background by displaying a small green or red icon, corresponding to the valid/invalid status of the input field.
You can simply use the input’s title attribute to provide a message for pattern-mismatches, and more generally for all validation errors. This solution is really neat and doesn’t require JavaScript!
Try the online example at JSBin, or try it here in your browser (type invalid values and look at the custom messages):
Type invalid values and see the result, this time with custom messages! E-mail: Homepage (URL): Phone number: Submit form
1. <form class="myForm"> 2. <fieldset> 3. <legend>Type invalid values and see the result</legend> 4. <label for="myEmail" class="formLabel">E-mail:</label> 5. <input type="email" id="myEmail" 6. title="You don't know what an email address looks like, do you?" 7. required/><br> 8. ... 9. <button>Submit form</button><br /> 10. </fieldset> 11. </form>
Beware that browser implementations may differ. Chrome, Opera will display the title attribute value in error message bubbles when the form is submitted, while Safari and FireFox (desktop and mobile) will simply ignore it.
You must also take care of the different languages, otherwise you will get error message bubbles that show some parts in the local language, and the message from the title attribute “as is”.
There is a JavaScript API for form validation. This API will let you use your own validation algorithm (i.e. check that you have entered the same password in two different input fields), and customize error messages. Also, together with some HTML/CSS/JavaScript you will be able to make your own message bubbles.
Be careful when you try this example in JS Bin standalone mode (click the small black arrow on the top right of the output tab).
Or, you may try it here in your browser.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of using the validation API</title> 6. <style> 7. .myForm input:invalid { background-color: lightPink;} 8. .myForm input:valid { background-color:lightGreen; } 9. .myForm input:required {border: 2px solid red;} 10. .myForm input:optional {border: 2px solid green;} 11. .myForm label { display: inline-block; width: 140px; text-align: right; } 12. </style> 13. </head> 14. <body> 15. <form class="myForm"> 16. <fieldset> 17. <legend>Example use of the validation API</legend> 18. <label for="password1" >Password:</label> <input type="password" id="password1" oninput="checkPasswords()" required> 19. <p> 20. <label for="password2"> Repeat password:</label> <input type="password" id="password2" oninput="checkPasswords()" required> 21. <p> 22. <button>Submit</button> 23. </fieldset> 24. </form> 25. 26. <script> 27. function checkPasswords() { 28. var password1 = document.getElementById('password1'); 29. var password2 = document.getElementById('password2'); 30. if (password1.value != password2.value) { 31. password2.setCustomValidity('Passwords non identiques'); 32. } else { 33. password2.setCustomValidity(''); 34. } 35. } 36. </script> 37. </body> 38. </html>
The validity API proposes a setCustomValidity() method available on input DOM objects. This method allows you to customize error messages. It takes a string parameter. When this string is empty, the element is considered valid, when the string is not empty, the field is invalid and the validation error message displayed in the bubble will be equal to that string.
At lines 18 and 20 we added an input event listener: each time a key is typed, the checkPasswords() function is called.
Lines 28 and 29 get the input fields’ values, and lines 30-35 check if the passwords are the same and set the validity of the field using the validation API’s method setCustomValidity(error_message).
The validity property of input fields helps to get error details when the field is invalid. This property tests the different types of validation error.
1. var input = document.getElementById('IdOfField'); 2. 3. var validityState_object = <b>input.validity;</b>
Here is an example at JSBin that shows how to test the different types of validation errors, or you may try it here in your browser (enter bad values, too big, too small, enter invalid characters, etc.):
1. <!DOCTYPE html> 2. <html lang="en"> 3. ... 4. <body> 5. 6. <script> 7. function validate() { 8. var input = document.getElementById('b'); 9. var validityState_object = input.validity; 10. 11. if(validityState_object.valueMissing) { 12. input.setCustomValidity('Please set an age (required)'); 13. } else if (validityState_object.rangeUnderflow) { 14. input.setCustomValidity('Your value is too low'); 15. } else if (validityState_object.rangeOverflow) { 16. input.setCustomValidity('Your value is too high'); 17. } else if (validityState_object.typeMismatch) { 18. input.setCustomValidity('Type mismatch'); 19. } else if (validityState_object.tooLong) { 20. input.setCustomValidity('Too long'); 21. } else if (validityState_object.stepMismatch) { 22. input.setCustomValidity('stepMismatch'); 23. } else if (validityState_object.patternMismatch) { 24. input.setCustomValidity('patternMismatch'); 25. } else { 26. input.setCustomValidity(''); 27. } 28. } 29. </script> 30. <form class="myForm"> 31. <label for="b">Enter a value between 10 and 20: </label> 32. <input type="number" name="text" id="b" min="10" max="20" required oninput='validate();'/> 33. <button>Submit</button> 34. </form> 35. 36. </body> 37. </html>
It is also possible to get the validation error message, using the validationMessage property of input fields.
1. var input = document.getElementById(b); 2. 3. console.log("Validation message = " + input.validationMessage);
This is useful for making custom error messages. More about this topic in the next section of the course.
Custom validation: changing the default behavior, aggregating error messages, removing bubbles, etc.
The techniques we have seen so far for enhancing HTML forms are powerful and provide interesting features, but are also criticized by Web developers:
However, the validation API gives enough power to make your own validation behavior, overriding the default when necessary.
Here is an adaptation of work presented at the developer.telerik.com Web site. This link is really worth reading, as it presents different approaches and gives external references for those who would like to go further.
Try the online example at JSBin, or try it here in your browser: enter invalid values and submit with one or two invalid fields.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Aggregating error messages</title> 6. <style> 7. input:invalid { background-color: lightPink;} 8. input:valid { background-color:lightGreen; } 9. input:required {border: 2px solid red;} 10. input:optional {border: 2px solid green;} 11. 12. .error-messages { 13. display: none; 14. margin: 0 10px 15px 10px; 15. padding: 8px 35px 8px 30px; 16. color: #B94A48; 17. background-color: #F2DEDE; 18. border: 2px solid #EED3D7; 19. border-radius: 4px; 20. } 21. fieldset { 22. border:1px solid; 23. padding:20px; 24. } 25. </style> 26. </head> 27. <body> 28. <form> 29. <fieldset> 30. <legend>Submit with one or two invalid fields</legend> 31. 32. <ul class="error-messages"></ul> 33. 34. <label for="name">Name:</label> 35. <input id="name" name="name" required> 36. <p> 37. <label for="email">Email:</label> 38. <input id="email" name="email" type="email" required> 39. <p> 40. <button>Submit</button> 41. </fieldset> 42. </form> 43. 44. <script> 45. function replaceValidationUI(form) { 46. // Suppress the default bubbles 47. form.addEventListener("invalid", function (event) { 48. event.preventDefault(); 49. }, true); 50. 51. // Support Safari, iOS Safari, and the Android browser -- each of which 52. // do not prevent form submissions by default 53. form.addEventListener("submit", function (event) { 54. if (!this.checkValidity()) { 55. event.preventDefault(); 56. } 57. }); 58. 59. // Container that holds error messages. By default it has a CSS 60. // display:none property 61. var errorMessages = form.querySelector(".error-messages"); 62. 63. var submitButton = form.querySelector("button:not([type=button]), 64. input[type=submit]"); 65. 66. submitButton.addEventListener("click", function (event) { 67. var invalidFields = form.querySelectorAll("input:invalid"); 68. var listHtml = ""; 69. var errorMessagesContainer = form.querySelector(".error-messages"); 70. var label; 71. 72. // Get the labels' values of their name attributes + the validation error 73. // message of the corresponding input field using the validationMessage 74. // property of input fields 75. // We build a list of <li>...</li> that we add to the error message container 76. for (var i = 0; i < invalidFields.length; i++) { 77. label = form.querySelector("label[for=" + invalidFields[ i ].id + "]"); 78. listHtml += "<li>" + 79. label.innerHTML + 80. " " + 81. invalidFields[ i ].validationMessage + 82. "</li>"; 83. } 84. 85. // Update the list with the new error messages 86. errorMessagesContainer.innerHTML = listHtml; 87. 88. // If there are errors, give focus to the first invalid field and show 89. // the error messages container by setting its CSS property display=block 90. if (invalidFields.length > 0) { 91. invalidFields[ 0 ].focus(); 92. errorMessagesContainer.style.display = "block"; 93. } 94. }); 95. } 96. 97. // Replace the validation UI for all forms 98. var forms = document.querySelectorAll("form"); 99. 100. for (var i = 0; i < forms.length; i++) { 101. replaceValidationUI(forms[ i ]); 102. } 103. </script> 104. </body> 105. </html>
Congratulations to all of you. Here, it’s summer and it’s hot, it’s time to get holidays, but before that we need to finish the course. I’m talking from the very nice park of the University of Nice in the south of France where I work. And I want you to be as happy as I am today to teach you this final week. We will look this time at the HTML5 persistence, notably at the HTML5 cache that is useful for creating Web sites and Web applications that work offline. We will also study the web storage API that is useful for saving and restoring data on the client side directly in browser.
It can be useful for setting some preferences for Web application, or for remembering what you typed in a form, for example. We will also look at another form of persistence using the file API. This JavaScript API is quite simple and can be used together with file choosers.
For example, you choose some pictures, some images you want to send to a server and using this API you can visualize them directly in the page before sending them to the server.
Finally, we will look at the geolocation API that is useful for locating the person that is using the browser.
You can provide a customized service that take into account the geolocation, or you can also display the position of the person in a map, we will see example that work with Google Map and with OpenStreetMap. Another classic use is automatic filling of the address input fields in a form.
Instead of typing the zip code, entering the city, entering the country, you can guess that automatically and propose the form that is already filled with the address. I really enjoyed teaching you this HTML5 basic course and I’m looking forward to meeting you the next HTML5 part-2 course that would address more advanced topics
We have already studied some of the HTML5 JavaScript APIs, including:
However, HTML5 also comes with several APIs that are not directly
related to HTML elements, namely: the Orientation API; the Geolocation
API; most APIs related to client-side persistence; the Web Storage API;
the Web Workers API; and some other APIs that are not in the HTML5
specification, but are related to it, such as the GamePad API, the Web
Audio API, etc.
This week, we will look at some of the most useful APIs. Others will be
covered in the W3C HTML5 Apps and Games course:
The Web storage API introduces “two related mechanisms, similar to HTTP session cookies, for storing structured data on the client side”.
Indeed, Web Storage provides two interfaces - sessionStorage and localStorage - whose main difference is data longevity. This specification defines an API for persistent data storage of key-value pair data in Web clients.
With localStorage the data will remain until it is deleted, whereas with sessionStorage the data is erased when the tab/browser is closed.
For convenience, we will mainly illustrate the localStorage object. Just change “local” to “session” and it should work (this time with a session lifetime).
localStorage is a simple key-value store, in which the keys and values are strings. There is only one store per domain. This functionality is exposed through the globally available localStorage object. The same applies to sessionStorage.
1. // using localStorage 2. 3. // store data 4. localStorage.lastName = "Bunny"; 5. localStorage.firstName = "Bugs"; 6. localStorage.location = "Earth"; 7. 8. // retrieve data 9. var lastName = localStorage.lastName; 10. var firstName = localStorage.firstName; 11. var location = localStorage.location;
This data is located in a store attached to the origin of the page. We created a JSBin example in which we included the above code.
Once opened in your browser, the JavaScript code is executed. With the browser dev. tools, we can check what has been stored in the localStorage for this domain:
Here is a view of the devtools. In more recent versions of Google Chrome, the “Resources” tab is named “Applications”:
Cookies are also a popular way to store key-value pairs. Web Storage, however, is a more powerful technique than cookies. The main difference is in size limits: cookies are limited to a few KBytes whereas Web Storage may extend to several MBytes. Also cookies generate additional HTTP request traffic (whether to request a Web page, an image, a stylesheet, a JavaScript file, etc.).
Objects managed by Web Storage are no longer carried on the network and HTTP, and are easily accessible (read, change and delete) from JavaScript, using the Web Storage API.
You can start filling this form and come back another day and complete it. It doesn’t matter if you closed your browser before coming back. The form never loses what you entered, even if you reload the page, or press “backspace” by mistake. This form auto saves/restores its content.
In this example, we use the most simple way to use localStorage:
Open this
online example at JSBin, and use F12 or cmd-alt-i (Mac OS) to look at the dev. tools. As you type in the
different input fields, their content is updated in the localStorage.
We just added input event listeners to each input field. For example, in order to save the first name input
field’s content, we just added:
oninput="localStorage.<font color="red">firstName</font>=this.value;"
Where firstName in red is the key and this.value the current value of the input field.
In the same way, we added an input listener to all the input fields in this example’s form.
This time, we want the form content to be restored on page load/reload. We will add a restoreFormContent() function in the JavaScript code that will be called each time the page is loaded. In this function, we will read the saved data and set the input fields’ values.
Complete example on JSBin: enter data and press reload at any time. The form content is restored!
1. // Called when the page is loaded 2. window.onload = restoreFormContent; 3. 4. function restoreFormContent() { 5. console.log("restoring form content from localStorage"); 6. 7. if(localStorage.firstName !== undefined) 8. document.getElementById("firstName").value = localStorage.firstName; 9. 10. if(localStorage.lastName !== undefined) 11. document.getElementById("lastName").value = localStorage.lastName; 12. 13. if(localStorage.email !== undefined) 14. document.getElementById("email").value = localStorage.email; 15. 16. if(localStorage.age !== undefined) 17. document.getElementById("age").value = localStorage.age; 18. 19. if(localStorage.date !== undefined) 20. document.getElementById("date").value = localStorage.date; 21. }
The tests at lines 7, 10, 13, etc., verify that data has been saved, before trying to restore it. Without these tests, it would put the “undefined” string as the value of input fields with no corresponding data to restore.
This time we will look at another example that uses new methods from the API:
If you want to keep a simple counter of the number of times a given user has loaded your application, you can use the following code (just to show how to use setItem/removeItem methods):
1. var counter = localStorage.getItem("count") || 0; 2. counter++; 3. localStorage.setItem("count", counter);
As you can easily guess from the above, we use var value = getItem(key) to retrieve a key’s value and setItem(key, value) to set it. This is similar to what we saw in the examples of the page above, except that this time:
1. var inputField = document.getElementById("firstName"); 2. saveInputFieldValue(inputField); 3. ... 4. function saveInputFieldValue(field) { 5. localStorage.setItem(field.id, field.value); 6. }
Deleting a key can be performed through removeItem(). And if you wish to reset the entire store, simply call localStorage.clear().
Note that it will probably only be the rare occasion that you will want the entire store to be cleared by the user in production software (since that effectively deletes their entire data). However, it is a rather a common operation needed during development, since bugs may store faulty data the persistence of which can break your application, since the way you store data may evolve over time, or simply because you also need to test the experience of the user when first using the application.
One way of handling this is to add a user interface button that calls clear() when clicked, but you must then remember to remove it when you ship! The recommended approach to use (whenever possible) is to simply open the dev. tool’s console and type localStorage.clear() there — it’s safer and works just as well.
Local stores (localStorage or sessionStorage) can also be iterated through in order to list all the content that they contain. The order is not guaranteed, but this may be useful at times (if only for debugging purposes!). The following code lists everything in the current store:
1. for (var i = 0, n = localStorage.length; i < n; i++) { 2. var k = localStorage.key(i); 3. console.log(k + ": " + localStorage[k]); // get the ith value, the one with a key that is in the variable k. 4. }
Students may note that something seems a bit off in the example above: instead of calling localStorage.getItem(k), we simply access localStorage[k]. Why? Because keys in the local store can also be accessed as if the store were a simple JavaScript object.
Instead of localStorage.getItem(“foo”) and localStorage.setItem(“foo”, “bar”), one can write localStorage.foo and localStorage.foo = “bar”. Of course there are limitations to this mapping: any string can serve as a key, so that localStorage.getItem(“one two three”) works, whereas that string would not be a valid identifier after the dot (but it could still work as localStorage[“one two three”]).
Online example at JSBin, run it, then click on the first button to show all key/values in the localStorage. Open the URL in another tab, and see that the data is shared between tabs, as local stores are attached to an origin.
Then click on the second button to add data to the store, click on the third to remove data. Finally, the last one clears the whole data store.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset=utf-8 /> 5. <title>Example of localStorare API use</title> 6. <script> 7. // Using localStorage 8. var counter = localStorage.getItem("count") || 0; 9. counter++; 10. localStorage.setItem("count", counter); 11. 12. function getCountValue() { 13. // retrieve data 14. document.querySelector("#counter").innerHTML = localStorage.count; 15. } 16. 17. function seeAllKeyValuePairsStored() { 18. // clear list first 19. document.querySelector('#list').innerHTML=""; 20. 21. for (var i = 0, n = localStorage.length; i < n; i++) { 22. var key = localStorage.key(i); 23. var value = localStorage[key]; 24. console.log(key + ": " + value); 25. 26. var li = document.createElement('li'); 27. li.innerHTML = key + ": " + value; 28. document.querySelector('#list').insertBefore(li, null); 29. } 30. } 31. 32. function resetStore() { 33. // erase all key values from store 34. localStorage.clear(); 35. // reset displayed list too 36. document.querySelector('#list').innerHTML=""; 37. } 38. 39. function addSomeData() { 40. // store data 41. localStorage.lastName = "Buffa"; 42. localStorage.firstName = "Michel"; 43. // refresh display 44. seeAllKeyValuePairsStored(); 45. } 46. 47. function removeSomeData() { 48. // store data 49. localStorage.removeItem("lastName"); 50. localStorage.removeItem("firstName"); 51. // refresh display 52. seeAllKeyValuePairsStored(); 53. } 54. </script> 55. </head> 56. <body onload="getCountValue()"> 57. <h1>Number of times this page has been seen on this browser: <span id="counter"></span></h1> 58. 59. <button onclick="seeAllKeyValuePairsStored()">Show all key value pairs stored in localStorage</button><br/> 60. <output id="list"></output> 61. 62. <button onclick="addSomeData()">Add some data to the store</button><br/> 63. <button onclick="removeSomeData()">Remove some data</button><br/> 64. <button onclick="resetStore()">reset store (erase all key/value pairs)</button> 65. </body> 66. </html>
You can check in the Chrome dev. tools user interface that the content of the localStorage changes as you click on the buttons.
Local stores are also useful for saving/restoring user preferences of Web Applications. For example, the JS Bin tool you have been using since the beginning of this course uses localStorage to store the list of tabs you open, and their width:
This way, the next time you come back to JSBin, “it will remember your last settings”.
Another example is a guitar FX processor / amp simulator your instructor is writing with some of his students. It uses localStorage to save/restore presets values:
Original example on JSBin:
We can change the color, size and speed of the animated rectangle. However, each time we come back to the page, default values are restored.
We would like to save the current values and find them back as they were when we come back to the page.
Here is a modified example that saves/restores its state, you can try it at JSBin. In this modified version of the animated rectangle example, you can set the color, size, speed, etc. And if you reload the page, the state of the different input field is restored, but also the internal variables. Check the source code in the JS Bin example and read the following explanations.
We used the same generic code for saving/restoring input fields’ values we saw in the first example that used localStorage. The only difference is that we renamed the two generic functions so that they correspond better to their role here (instead of saveFormContent we called the function restorePreferences).
The function initPreferences is executed when the page is loaded.
1. function initPreferences() { 2. console.log("Adding input listener to all input fields"); 3. // add an input listener to all input fields 4. var listOfInputsInForm = document.querySelectorAll("input"); 5. for(var i= 0; i < listOfInputsInForm.length; i++) { 6. addInputListener(listOfInputsInForm[i]); 7. } 8. // restore preferences 9. restorePreferences(); 10. applyGUIvalues(); // Use the input fields' values we just restored to set internal 11. // size, incX, color, lineWidth variables 12. } 13. 14. function addInputListener(inputField) { 15. // same as before 16. } 17. 18. function restorePreferences() { 19. // same as old restoreFormContent 20. } 21. 22. function applyGUIvalues() { 23. // Check restored input field content to set the size of the rectangle 24. var sizeWidget = document.getElementById("size"); 25. size = Math.sign(incX)*parseInt(sizeWidget.value); 26. // also update the outline element's value 27. document.getElementById("sizeValue").innerHTML = size; 28. // Check restored input field content to set the color of the rectangle 29. var colorWidget = document.getElementById("color"); 30. ctx.fillStyle = colorWidget.value; 31. // Check restored input field content to set the speed of the rectangle 32. var speedWidget = document.getElementById("speed"); 33. incX = Math.sign(incX)*parseInt(speedWidget.value); 34. // also update the outline element's value 35. document.getElementById("speedValue").innerHTML = Math.abs(incX); 36. // Check restored input field content to set the lineWidth of the rectangle 37. var lineWidthWidget = document.getElementById("lineWidth"); 38. ctx.lineWidth = parseInt(lineWidthWidget.value); 39. }
This time, using the setItem and getItem method we saw earlier in the course, we could write some generic functions for saving/restoring input fields’ content, without having advance knowledge about the number of fields in the form, their types, their ids, etc.
Furthermore, we removed all input listeners in the HTML, making it cleaner (no more oninput=“localStorage.firstName = this.value;’…)
Define listeners + restore old values after the page is loaded, use generic functions
We start writing an init() function that is called when the page is loaded. This function will:
1. // Called when the page is loaded 2. window.onload = init; 3. 4. function init() { 5. console.log("Adding input listener to all input fields"); 6. // add an input listener to all input fields 7. var listOfInputsInForm = document.querySelectorAll("input"); 8. for(var i= 0; i < listOfInputsInForm.length; i++) { 9. addInputListener(listOfInputsInForm[i]); 10. } 11. // restore form content with previously saved values 12. restoreFormContent(); 13. }
And here is the addInputListener(inputField) function. It takes an input field as parameter and attaches an oninput listener to it, that will save the field’s content each time a value is entered. The key will be the id of the input field (line 3):
1. function addInputListener(inputField) { 2. inputField.addEventListener('input', function(event) { 3. localStorage.setItem(inputField.id, inputField.value); 4. }, false); 5. }
Note that at line 2, we use addEventListener (that is not using the oninput property here). adddEventListener doesnot replace existing oninput definitions and keep all existing listeners unchanged.
We have seen how to save all input fields’ content on the fly. Now, let’s see how we can restore saved values and update the form. This is done using the function restoreFormContent():
1. function restoreFormContent() { 2. console.log("restoring form content from localStorage"); 3. // get the list of all input elements in the form 4. var listOfInputsInForm = document.querySelectorAll("input"); 5. // For each input element, 6. // - get its id (that is also the key for it's saved content 7. // in the localStorage) 8. // - get the value associated with the id/key in the local 3. // storage 10. // - If the value is not undefined, restore the value 11. // of the input field 12. for(var i= 0; i < listOfInputsInForm.length; i++) { 13. var fieldToRestore = listOfInputsInForm[i]; 14. var id = fieldToRestore.id; 15. var savedValue = localStorage.getItem(id); 16. if(savedValue !== undefined) { 17. fieldToRestore.value = savedValue; 18. } 19. } 20. }
In this function, we first get the list of input fields (line 5), then iterate on it (line 14). For each input field, we get its id, which value is the key in localStorage for the previous data saved for this field (lines 15-16). Then if the value is not undefined, we restore it by setting the value of the input field (lines 19-20).
These generic functions can be used in many different projects
Indeed, if you look carefully, you will see that these functions are really useful. You may easily embed them in your own projects, or perhaps adapt them for a particular need (i.e. for saving input type=“checkboxes” that work a bit differently), etc.
Few things to remember, from the Web storage specification:
In many cases, local storage is all that your application will need for saving/loading data on demand. More complex ways to do it exist, such as IndexedDB, a No SQL database, that proposes transactions and usually comes with far more available space than local storage. IndexedDB usage is for advanced users and will be covered in the W3Cx HTML5 Apps and Games.
Additionally, there will be a limit on the amount of data that you can store there. Browsers enforce quotas that will prevent you from cluttering your users’ drives excessively. These quotas can vary from platform to platform, but are usually reasonably generous for simple cases (around 5MB), so if you are careful not to store anything huge there, you should be fine.
Finally, keep in mind that this storage is not necessarily permanent. Browsers are inconsistent in how they allow for it to be wiped, but in several cases it gets deleted with cookies — which is logical when you think of how it can be used for tracking in a similar fashion.
For serious applications, you might want to synchronize existing data with the server on a regular basis, in order to avoid data loss (and in general, because users enjoy using the same service from multiple devices at once). This is a rather complex feat, and frameworks such as Firebase can help. Such techniques are beyond the scope of this course and will not be covered.
sessionStorage key/values instead of cookies?
Note that if all you need is to store session-based data in a manner that is more powerful than cookies, you can use the sessionStorage object which works in exactly the same way as localStorage, but the lifetime is limited to a single browser session (lifetime of your tab/window).
Also note that in addition to being more convenient and capable of storing more data than cookies, it has the advantage of being scoped to a given browser tab (or similar execution context).
Cookies’ security drawback: if a user has two tabs open to the same site, they will share the same cookies. Which is to say that if you are storing information about a given operation using cookies in one tab, that information will leak to the other side — this can be confusing if the user is performing different tasks in each.
By using sessionStorage, the data you store will be scoped and therefore not leak across tabs!
Storing strings is all well and good, but it quickly becomes limiting: you may want to store more complex data with at least a modicum of structure.
There are some simple approaches, such as creating your own minimal record format (e.g. a string with fields separated with a given character, using join() on store and split() upon retrieval) or using multiple keys (e.g. post_17_title, post_17_content, post_17_author, etc.). But these are really hacks. Thankfully, there’s a better way, JSON.stringify() and JSON.parse() methods.
JSON provides a great way of encoding and decoding data that is a really good match for JavaScript. You have to be careful not to use circular data structures or non-serializable objects, but in the vast majority of cases, plugging JSON support into your local store is straightforward.
locaStorage.key = JSON.stringify(object); // or... localStorage.setItem(key, JSON.stringify(object));
Let’s try a simple toy example (online at JSBin). The example below saves a JavaScript object in JSON, then restores it and checks that the object properties are still there!
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset=utf-8 /> 5. <title>Storing JSON Objects with Local Storage</title> 6. <script> 7. var personObject= {'givenName': 'Michel', 'familyName': 'Buffa'}; 8. // Store the object as a JSON String 9. localStorage.setItem('testObject', JSON.stringify(personObject)); 10. // Retrieve the object from storage 11. var retrievedObject = JSON.parse(localStorage.getItem('testObject')); 12. console.log(retrievedObject.firstName + " " + retrievedObject.lastName); 13. // then you can use retrievedObject.givenName, retrievedObject.familyName... 14. </script> 15. </head> 16. <body> 17. </body> 18. </html>
Online example on JSBin that saves in localStorage an array of contacts in JSON
Add contacts using the form, see how the HTML table is updated. Try to reload the page: data are persisted in localStorage.
Examine the localStorage:
The source code for this example is a bit long, and we suggest that you examine it in the JS Bin tool. We extensively commented it. It uses:
Well structured pages with the new elements seen during Module 1 (section, article, nav, aside, etc.)
HTML5 form elements with builtin and custom validation (the date cannot be in the past, the firstName and lastName fields do not accept &, #, ! or $ characters), localStorage for saving / restoring an array of contacts in JSON.
It shows how to use the DOM API for dynamically updating the page content (build the HTML table from the array of contacts, add a new line when a new contact is submitted, etc.)
The objective of this chapter is to provide an overview of the File API.
Before HTML5, file management was limited to multipart forms and to Ajax for sending/requesting files to/from a remote Web server.
Possible actions were limited, both for the developer and the user. However, HTML5 now comes with an API called “File” that holds features for accessing file metadata (name, size, type) from client-side JavaScript. The API also has methods for reading file contents directly in the browser. This is particularly interesting for displaying preview of images before uploading them, or - and this is much more interesting - for developing Web applications that work with local files without the need for a server.
Imagine a multimedia player that accesses (in read-only) your file system, reads your audio and video files, etc., such as the Remo Music player below, or an application that edits the audio content of local mp3 files, for example, the HYA-WAVE sound editor (screenshot above).
Hi! Welcome for this second video of module 6, in which we will talk about the file Javascript API.
This is an API that has been designed for working with files on the client side in Javascript.
Before HTML5, there was no mean to know the the size, the name, the modification date of a file without sending it to a remote server. It was not possible also to read the content of a file, for example for previewing an image before uploading it into a remote server, or if you wanted to play a local audio file, or a video file without streaming and downloading it from a remote server. It was not possible.
I’m going first to show you some example of server less applications.
I mean applications written using HTML, CSS and Javascript that do not rely on data located on a remote server.
For example, I can show you this. This is a wave. This is an audio file editor that works with local files. I open the page, and from here I can drop a music file directly. Let’s go to get some music somewhere. Some AC/DC song! I can just drop the song and I can edit the song. I can play the song. I can cut, I can save locally, etc.
Another example is an application, a Chrome application that is also written using Web technologies but doesn’t run using HTTP.
It’s quite easy to write such applications, because there are regular Web applications using HTML, CSS and Javascript except that you’ve got to package them for putting them on the Chrome store.
I’m going first to show you a local audio file player that is called Remo.
I can just open files, choose one or several, open the files, I’ve got a playlist and I can play the songs. Normally it works… yes, OK.
Finally, I’m going to show you an image editor that has been written by one of the students from this course. This is an example of Instagram-like filters that have been written by @GeorgianaB from the discussion forum. It’s a regular Web application except that you can upload an image, a local image. Ok, no, this one is too small maybe. You can upload an image… it’s me with a strange head ;) Then you can apply Instagram filters or add borders. You can even download the result to your local disk.
We saw how to do this when we look at the download attributes in Module 1.
All these examples work with local files. Let’s go back to the course and now I’m going to show you how we can, for example, read images using an input type=file like this.
Select several files, open, and look at the preview directly in the page.
Here, I just read the files using the File API, I read them as what we call data URL and I created some img elements we added in the page.
First, before writing the application, I want to talk a little bit about data URLs.
Data URLs are strange set of characters like that ,and if you select them and paste them in the address bar you can see a small red icon. And you can use this everywhere where you would have use an http://URL.
If I use this image here, I can using it in JSBin for example, and if I add the image here I can see that the content of the image, the pixels, are encoded in the URL itself. How can you make such URLs? You’ve got plenty of Web sites, look for data URL with a search engine and you’ve got plenty of them.
The first one here is called DATAURL.net and it included ‘Data URL Maker’ so you can just open a file, any sort of file, here I take a JPG file and it will produce the data URL that corresponds. But you can also use with mp3 file, or mp4 file video or any sort of file encoding.
This was to introduce this new sort of URL that maybe you’ve never heard about before. Now, how can we write this?
I’m going to explain with the source code here because it’s simpler.
After that I will code it live. What we do is that we use an input type=file for reading the files. We define an event listener that will call this function ‘read files and display preview’ once the user has chosen some files.
In this callback, we iterate, we’ve got a ‘for’ loop, about the files. For each file, we build a file reader, that is a special object, that has a function called ‘readAsDataURL’, that will take a file descriptor as a parameter here.
When the file is read, because that can take some time if the file is big, then each file is read, the onload callback is executed. It’s exactly the same principle we saw with images as reading an image can take some time. You can only work with that image in an onload callback. So, when we enter this function that means that one file has been read and we can get the content of the file through the event using the ‘e.target.result’ property. Each time I read a file I can get its content here and it’s a data URL that contains all the pixels.
What we do in that example, is that we create a <span>. In the span with the innerHTML, we add an image that has for src attribute the content of the image we read as a data URL.
And finally, we just insert this image in the document and this is when we see it.
I’m going to live code this one, maybe a simpler version so that you can see the different steps in real time. First, I add an ‘input type=file multiple’ for selecting multiple files. This is what’s happening and here I can select several images. I’m going to add an ‘onchange’ listener:
'onchange=readImagesAndPreview()'.
And I’m going to pass the set of file descriptors that have been selected; ‘this.files’ is the correct way to send the selected files to a Javascript function.
Now, I’m going to start writing some script. But before, I will add a space, let’s say ‘div id=thumbnails’.
This is an empty container that will be used for inserting the preview of the images.
Now I’m going in the script part. In the script part, we’re going to write this function ‘readImagesAndPreview()’ and it takes as parameter the files that will be sent.
And here if I want just to read the first file I will just show you how it works. I create a reader object, like that, and I ask the reader to ‘readAsDataURL’ the first file.
What is happening is that this may take some time and I need to declare, before reading the files, a callback that will be code only once the file would have been read - ‘reader.onload’
And here, what can I do with the file that has been read, I will create an image element.
So i created an image element, I set the source of this element with the file content that is in the ‘e.target.value’. So I created an image. Then, I will add the image to the container here. I’m going to declare the container here. How can I add an element: ‘container.appendChild’ and I add the image I created.
Let’s try it, maybe I made some mistakes. I select several files, I open them and here, apparently, I make a small mistake.
Let’s open an image and now read the image and I can see the preview.
If you look carefully, it’s just a few lines of code.
So now I’m going to show you how we could work with not only one single image, but several images. The trick consists in adding a ‘for’ loop.
We will iterate on the length of the file’s variable here. If we got 3 files, we do 3 loops. I’m going to define a variable called ‘f’ that will correspond to the current file.
What we are going to read this time is the current file ‘f’.
So this loop here, will first read files, for each one we do read as data URL, when the file is read we enter the onload callback, we create an image with the result.
I’m going just to add some constraints on the size, because when I tried earlier I had big images that could not fit on the screen.
I constraint the width to 100px and the height will just follow.
Like this, let’s try. If I select several pictures, two of them,
I can see the two pictures. If I select more, then I can see the whole set of pictures that are coming as previews.
You can see that the number of lines of code is really small and this is quiet powerful.
If you want to write, for example, a Web site that will upload pictures, you can preview them, select them and then upload them.
I show you earlier the application by one of the students that can do Instagram like filters, but you can also find some new on the Chrome store.
I installed one that is called Polarr that is using HTML for all the functionalities. So you can just import a picture, let’s say this one. Then you can select filters, you can do some corrections on it and so on, and finally you can export it and that’s all.
All this here is HTML5, this is canvas, these are form elements we saw the last week, some canvas here, or image elements and so on.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of using readAsDataURL</title> 6. </head> 7. <body> 8. <input type="file" multiple onchange="readImagesAndPreview(this.files);"> 9. <p> 10. <div id="thumbnails"></div> 11. <script> 12. var container = document.getElementById("thumbnails"); 13. function readImagesAndPreview(files) { 14. for(var i=0; i < files.length; i++) { 15. var f = files[i]; 16. var reader = new FileReader(); 17. reader.onload = function(e) { 18. var img = document.createElement("img"); 19. img.src = e.target.result; 20. img.width = 100; 21. container.appendChild(img); 22. } 23. reader.readAsDataURL(f); 24. } 25. } 26. </script> 27. </body> 28. </html>
Imagine you have an input field like this:
This renders as a “select files” or “browse files” button. If you select one file in the file chooser dialog that has popped up, before HTML5 you couldn’t do anything with it in the client-side: no access from JavaScript. With the File API, you can read what we call “file metadata”: name, size, type and last modification date.
Look at the code below: the file API defines a files property on the DOM node corresponding to the <input type=“file”…/> input field. This property is an array.
In the example below, we get in the selectedFile variable, the metadata related to the first selected file:
var selectedFile = document.getElementById('input').files[0]; // do something with selectedFile.name, selectedFile.size, selectedFile.type // selectedFile.lastModifiedDate ...
Here is a complete example on JSBin that uses the code above to get details about the first selected file. Please try it below on your browser (click on the button and choose one file):
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset=utf-8 /> 5. <title>Reading file metadata</title> 6. <script> 7. function displayFirstSelectedFileMetadata() { 8. var selectedFile = document.getElementById('input').files[0]; 9. document.querySelector("#singleName").innerHTML = selectedFile.name; 10. document.querySelector("#singleSize").innerHTML = selectedFile.size + " bytes"; 11. document.querySelector("#singleType").innerHTML = selectedFile.type; 12. document.querySelector("#singleDate").innerHTML = selectedFile.lastModifiedDate; 13. } 14. </script> 15. </head> 16. <body> 17. Select one or more files: <input type="file" id="input" 18. onchange="displayFirstSelectedFileMetadata();"/> 19. <p> 20. <ul> 21. <li>File name: <span id="singleName"></span></li> 22. <li>File size: <span id="singleSize"></span></li> 23. <li>File type: <span id="singleType"></span></li> 24. <li>File last modification date: <span id="singleDate"></span></li> 25. </ul> 26. </body> 27. </html>
This example is a bit more complicated, as it will display details about all files selected (not only the first) and allows only images to be selected, using the accept attribute of the input field: <input type=“file” accept=“image/*“…/>.
Example on JSBin, or try it in your browser: click on the button, and select multiple image files. Notice that in the file selector, files that are not images will be greyed and non selectable.
1. <p>Select several images: <input type=“file” accept=“image/*“ multiple onchange=”filesProcess(this.files)“ name=”selection”/></p> 2. <div id="result">...</div> 3. <script> 4. function filesProcess(files) { 5. var selection = "<table><tr><th>Name< /th><th>Bytes</th><th>MIME Type</th> 6. <th>Last modified date</th></tr>"; 7. for(i=0; i<files.length ;i++){ 8. file = files[i]; 9. selection += "<tr><td>"+file.name+"</td><td style="text-align:right">" 10. +file.size+"</td><td>" 11. +file.type+"</td><td> "+file.lastModifiedDate+"</td></tr>"; 12. } 13. selection += "</table>"; 14. document.getElementById("result").innerHTML = selection; 15. } 16. </script>
The HTML5 File API specification introduces several interfaces:
We will not use all of these interfaces, but let’s explain the difference between Blob and File, as most of the methods exposed by the FileReader interface take indiscriminately a Blob or a File as parameter.
An object of type Blob is a structure that represents binary data available as read-only. Most of the time, you will only encounter these objects when you handle files.
Blob objects have two properties, namely:size and type, which respectively retrieve the size in bytes of the data handled by the Blob and their MIME type.
File objects are useful for manipulating… files! They inherit the properties and methods of Blob objects, and have two additional properties that are name, for the file name, and lastModifiedDate to get the date of the last modification of the file (in the form of a JavaScript Date object, obviously) .
Most of the time, we will work with File objects. Blob objects will have real interest when you download binary files using Ajax (see example below).
If you are interested in seeing how Blob objects can be used, here is an example “as is” that shows how to download an image using Xhr2 (Xml Http Request version 2). The examples uses a <progress> element to show the download progress, and uses xhr.responseType = ‘blob’; to indicate that the file we are going to download is a binary file (a blob). Try the example, then comment the line with responseType=‘blob’. In this case, you will notice that the image file is not properly decoded by the browser and is not displayed in the page. We explain Xhr2 in the W3C HTML5 Apps and Games course.
In order to read the content of a file, different steps required. Let’s see how to do it.
The file API proposes several methods for reading file content, each taken from the FileReader interface. Here is how you create a FileReader object:
There are three different methods available for reading a file’s content: readAsText, readAsArrayBuffer for binary data and also as readAsDataURL (the content will be a URL you will use to set the src field of an <img src=...>, <audio>, <video>, and also with all existing methods/properties that accept a URL).
All these methods take as a unique parameter a File object (for example, a file chosen by a user after clicking on a <input type=file> input field). Below, we use, as an example, the readAsText method:
1. function readFileContent(f) { 2. // Executed last: called only when the file content is loaded, e.target.result is 3. // The content 4. reader.onload = function(e) { 5. var content = e.target.result; 6. // do something with the file content 7. console.log("File " + f.name + " content is: " + content); 8. }; 9. // Executed first: start reading the file asynchronously, will call the 10. // reader.onload callback only when the file is read entirely 11. reader.readAsText(f); 12. }
The above code shows how a file can be read as text. The function is called, for example by clicking on the button corresponding to a <input type=“file” id=“file” onchange=“readFileContent(this.files)”/>, and by choosing a file.
Try a variation of the above code in your browser, that displays the file content in a text area. This example is detailed further in the course. Click and select a text file below:
Choose a text file:
In the following pages, we look at different examples that read file contents as text, dataURL and binary.
Let’s start by reading a pure text file
Example at JSBin, or try it below in your browser:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of use of FileReader with a text file</title> 6. </head> 7. <body> 8. <label for="files">Choose a text file:</label><input type="file" id="file" 9. onchange="readFileContent(this.files)"/><br/> 10. <p> 11. <textarea rows=15 cols=50 id="fileContent"></textarea> 12. 13. <script> 14. function readFileContent(files) { 15. console.log("In readFileContent"); 16. var reader = new FileReader(); 17. 18. // Executed last: called when the file content is loaded, e.target.result is 19. // The content 20. reader.onload = function(e) { 21. // display content in the textarea with id="fileContent" 22. document.getElementById("fileContent").value= e.target.result; 23. }; 24. 25. console.log("Reading file:" + files[0].name); 26. 27. // Executed first: start reading the file asynchronously , will call the onload 28. // callback when the file is read 29. reader.readAsText(files[0]); 30. } 31. </script> 32. </body> 33. </html>
This example is the one at the end of the previous page. This time, we show the complete source code above. Remember that the instruction at line 29 is executed first, then when the file is read, the browser will call asynchronously the onload callback at line 20.
Example on JSBin, or try it below in your browser. This time, please select multiple text files (using shift for multiple selection):
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Example of use of FileReader with a text file</title> 6. </head> 7. <body> 8. <label for="files">Choose multiple text files:</label> 9. <input type="file" id="files" 10. multiple onchange="readFilesAndDisplayAsText(this.files);"/><br/> 11. <p> 12. <textarea rows=30 cols=50 id="filesContent"></textarea> 13. 14. <script> 15. var filesContent = document.getElementById("filesContent"); 16. 17. function readFilesAndDisplayAsText(files) { 18. console.log("dans read files"); 19. // Loop through the FileList 20. for (var i = 0, f; f = files[i]; i++) { 21. 22. var reader = new FileReader(); 23. 24. // Add an onload listener to the reader 25. addOnLoadListener(reader, f.name); 26. // start reading, will call the listener later, when the file f is read 27. reader.readAsText(f); 28. 29. } 30. } 31. 32. function addOnLoadListener(reader, name) { 33. // Add an onload listener that will be able to print the name of the 34. // file... 35. reader.onload = function(e) { 36. filesContent.value += "###### READING FILE " + name + " ######"; 37. filesContent.value += e.target.result; 38. }; 39. } 40. </script> 41. </body> 42. </html>
This example is similar to the previous one, except that this time we read multiple files.
Line 20: this is the for loop that will iterate on the files object passed as parameter by the onchange listener declaration at line 10.
Line 25: instead of declaring the onload listener with a reader.onload =… directly in the loop, this time we preferred to write a separate function that will do this. This technique is useful when you want the listener to work with extra variables computed in the loop (in our case, the name of the file).
Note that you can optionally indicate the encoding of the file you are going to read (default is UTF-8):
reader.readAsText(file, 'UTF-8'); reader.readAsText(file, 'ISO-8859-1'); ...
This method is rarely used, except for loading “raw” binary data. For images you would like to see in your HTML page using the <img src= tag> or for drawing in a canvas, or for audio and video files that you would like to play using the <audio> or <video> elements, it would be preferable to use the readAsDataURL method presented on the next page of the course.
readAsArrayBuffer is often used for purposes such as reading audio samples that should be loaded in memory and played using the WebAudio API, or for loading textures that you will use with WebGL for 3D animations.
The WebAudio API is useful for reading audio sound samples from memory (no streaming), and has been designed for music application and games. This example shows how a local audio file can be read and played directly in the browser, without the need for a server!
Example on JSBin (does not work on IE, as it does not support the WebAudio API). We could not embed it here on the edX platform as it prevents code that uses Ajax to run in its pages.
1. // User selects file. Read it as an ArrayBuffer and pass to the API. 2. var fileInput = document.querySelector('input[type="file"]'); 3. fileInput.addEventListener('change', function(e) { 4. var reader = new FileReader(); 5. reader.onload = function(e) { 6. initSound(e.target.result); 7. }; 8. // THIS IS THE INTERESTING PART! 9. reader.readAsArrayBuffer(this.files[0]); 10. }, false);
A data URL is a URL that includes type and content at the same time. It is useful, for example, for in-lining images or videos in the HTML of a Web page (on mobile devices, this may speed up the loading of the page by reducing the number of HTTP requests).
Here is an example of a red square, as a data URL. Copy and paste it in the address bar of your browser, and you should see the red square:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
If we set the src attribute of an image element <img src="data:image/png...."> with the data URL of the above screenshot, it will work exactly as if you used a URL that started with https://
In your browser, you will see a small red circle rendered by this source code:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO 9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red square" width=50 height=50/>
This dataURL format enables file content to be stored in a base64 format (as a string), and adds the MIME type specification of the content. The dataURL can therefore store a file as a URL readable with modern browsers. It is becoming more commonly used on the Web, especially for mobile applications, as inlining images reduces the number of HTTP requests and makes the Web page load faster.
You will find lots of Web sites and tools for generating dataURL from files, such as Image to Data URI converter (screenshot below):
With the above example, you can copy and paste the characters on the left and use them with an <img src="...">. Just set the src attribute with it!
Notice that you can encode any type of file as dataURL, but this format is most frequently used with ./images files (images, audio, video).
Example of HTML5 logo embedded in a document without any real image, just a dataURL and CSS:
This first example is useful for forms that allow the user to select one or more pictures. Before sending the form, you might want to get a preview of the pictures in the HTML page. The reader.readAsDataUrl method is used for that.
Example on JSBin or try it below in your browser:
1. <label for="files">Choose multiple files:</label> 2. <input type="file" id="files" multiple 3. onchange="readFilesAndDisplayPreview(this.files);"/><br/> 4. <p>Preview of selected images:</p> 5. <output id="list"></output> 6. <script> 7. function readFilesAndDisplayPreview(files) { 8. // Loop through the FileList and render image files as thumbnails. 9. for (var i = 0, f; f = files[i]; i++) { 10. // Only process image files. 11. if (!f.type.match('image.*')) { 12. continue; 13. } 14. var reader = new FileReader(); 15. 16. //capture the file information. 17. reader.onload = function(e) { 18. // Render thumbnail. e.target.result = the image content 19. // as a data URL 20. // create a span with CSS class="thumb", for nicer layout 21. var span = document.createElement('span'); 22. // Add an img src=... in the span, with src= the dataURL of 23. // the image 24. span.innerHTML = "<img class='thumb' src='" + 25. e.target.result + "' alt='a picture'/>"; 26. // Insert the span in the output id=list 27. document.getElementById('list').insertBefore(span, null); 28. }; 29. // Read in the image file as a data URL. 30. reader.readAsDataURL(f); 31. } 32. }
Errata: the above screenshot says "choose multiple files", but the example only works with a single file.
1. function drawImage(imageFile) { 2. var reader = new FileReader(); 3. 4. //capture the file information. 5. reader.onload = function(e) { 6. // For drawing an image on a canvas we 7. // need an image object 8. var img = new Image(); 9. // Even if the file has been read, decoding 10. // the dataURL format may take some time 11. // so we need to use the regular way of 12. // working with images: onload callback 13. // that will be called after setting the src attribute 14. img.onload = function(e) { 15. // draw the image! 16. ctx.drawImage(img, 0, 0, 400, 400); 17. } 18. // e.target.result is the dataURL, so we set the 19. // src if the image with it. This will call 20. // asynchonously the onload callback 21. img.src= e.target.result; 22. }; 23. // Read in the image file as a data URL. 24. reader.readAsDataURL(imageFile); 25. } 26. function readFileAndDraw(files) { 27. drawImage(files[0]); 28. }
Remember how we worked with images on a canvas. We had to create an empty image object (line 8), set the src attribute of the image object (line 23), then use an image.onload callback (line 15), and we could only draw from inside the callback (line 17). This time, it’s exactly the same, except that the URL comes from e.target.result in the reader.onload callback (line 23).
Another very impressive example, has been developed by @GeorgianaB, a student of the first iteration of this course (see her other creations/examples). This Web application reads local image files, draws them into a canvas element and proposes different filters. This example is given “as is” for those of you who would like to go further. Just click on the link (or on the image below) and look at the source code.
Try this example online on gitHub.
This chapter presents the new Geolocation API and illustrates its use with several examples.
The Geolocation HTML5 JavaScript API is implemented by most modern Web browsers, and uses different means to get the current location: GPS, GSM/3G triangulation, Wifi, IP address, etc.
It is possible to prompt the user to activate the GPS (this is what
most GPS navigation software does on mobile phones), or ask for a
particular mean among those available. It is also possible to track the
current position when it changes. This is useful for writing a
navigation application or for tracking in real time the position of
different participants in the case of an application that involves
several persons at the same time (using WebSockets, for example).
Browser support for the Geolocation API is excellent, both on mobile and on desktop devices.
navigator.geolocation.getCurrentPosition(showPosition, onError); function showPosition(position) { console.log("latitude is: " + position.coords.latitude); console.log("longitude is: " + position.coords.longitude); } function onError(err) { console.log("Could not get the position"); }
This online example at JSBin shows how to get the current longitude and latitude and display them in an HTML page. Try it below in your browser:
Click the button to get your coordinates: Where am I ? Note that the first time you execute this example, for privacy reasons, the browser will ask if you agree to share your position with the application.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Basic example of use of the geolocation API</title> 6. </head> 7. <body> 8. <p id="msg">Click the button to get your coordinates:</p> 9. <button onclick="getLocation()">Where am I ?</button> 10. <script> 11. var displayCoords=document.getElementById("msg"); 12. function getLocation() { 13. if (navigator.geolocation) { 14. navigator.geolocation.getCurrentPosition(showPosition); 15. } else { 16. displayCoords.innerHTML="Geolocation API not supported by your browser."; 17. } 18. } 19. function showPosition(position) { 20. displayCoords.innerHTML="Latitude: " + position.coords.latitude + 21. "<br />Longitude: " + position.coords.longitude; 22. } 23. </script> 24. </body> 25. </html>
In the previous example, we used the coords property of the position passed as an input parameter to the callback function. This coords object has many properties:
Not all these values may be available in all Web browsers. When one of these properties is null, it means that it is not available (often the case of the altitudeAccuracy)
In the last example, we used the navigator.geolocation. getCurrentPosition(showPosition) with only one callback function (in the case of success), but it is also possible to pass a second parameter that is another callback function called in the case of error.
A slightly different version of the previous example shows how to properly check against the different possible errors. Try it, then turn your WiFi off or unplug your Ethernet cable (or turn off GPS and 3G/4G on a mobile phone). You should see an error message Error during geolocation: Location could not be obtained though the available means”:
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Basic example of use of the geolocation API</title> 6. </head> 7. <body> 8. <p id="msg">Click the button to get your coordinates:</p> 9. <button onclick="getLocation()">Where am I ?</button> 10. <script> 11. var displayCoords=document.getElementById("msg"); 12. function getLocation() { 13. if (navigator.geolocation) { 14. navigator.geolocation.getCurrentPosition(showPosition, errorPosition); 15. } else { 16. displayCoords.innerHTML="Geolocation API not supported by your browser."; 17. } 18. } 19. function showPosition(position) { 20. displayCoords.innerHTML="Latitude: " + position.coords.latitude + 21. "<br />Longitude: " + position.coords.longitude; 22. } 23. function errorPosition(error) { 24. var info = "Error during geolocation: "; 25. switch(error.code) { 26. case error.TIMEOUT: 27. info += "Timeout !"; 28. break; 29. case error.PERMISSION_DENIED: 30. info += "Permission denied, geolocation could not be obtained..."; 31. break; 32. case error.POSITION_UNAVAILABLE: 33. info += "Location could not be obtained though the available means..."; 34. break; 35. case error.UNKNOWN_ERROR: 36. info += "Unknown error"; 37. break; 38. } 39. displayCoords.innerHTML = info; 40. } 41. </script> 42. </body> 43. </html>
In order to track the current position, the geolocation API provides a method similar to the getCurrentPosition(onSuccess, onError) named watchPosition(onSuccess, onError).
When getCurrentPosition gives a position when called, watchPosition does the following:
// get an id of the current tracking, the showPosition callback is like the one we saw in earlier examples. var watchPosId = navigator.geolocation.watchPosition(showPosition); ... // stop the tracking navigator.geolocation.clearWatch(watchPosId);
As a test, you may just try to change getCurrentPosition to watchPosition in the previous examples, and try this code using a mobile phone or tablet, walk for 20 meters and see the position changing.
Several options are available when using HTML5 geolocation. We can pass a third parameter to the getCurrentPosition and watchPosition methods, that will hold one or several of the following options:
1. // Just ask to turn GPS on, if available 2. navigator.geolocation.getCurrentPosition(onSuccess, onError, 3. {enableHighAccuracy:true}); 4. // maximumAge = 10 mins, the position can be cached for 10 mins, 5. // useful when in tunnels...When the device tries to get 6. // a position, if it does not succeed, then go on error 7. // immediately 8. navigator.geolocation.getCurrentPosition(onSuccess, onError, 9. {maximumAge:600000, timeout:0}); 10. // Position will never come from the cache (maximumAge: 0), and 11. // if after 0.1s the position could not be computed, then go on 12. // error 13. navigator.geolocation.getCurrentPosition(onSuccess, onError, 14. {maximumAge:0, timeout:100}); 15. // Ask for GPS, cache for 30s, 27s before going on error... 16. watchId=navigator.geolocation.watchPosition(onSuccess, onError, 17. {enableHighAccuracy:true, maximumAge:30000, timeout:27000});
Look for the explanations in the lines of comment.
This section presents an example of how to get an interactive map, using the Leaflet API for OpenStreetMap, and gives links to more resources. Did you know that you can even get an estimation of a physical address from the longitude and latitude, using online Web services?
How to get a map centered on your longitude and latitude.
This example is just given "as is", as there are so many possibilities for rendering a map with the Leaflet API for OpenStreetMapshttps:/. However, we think having such a basic example might be useful.
1. function getLocation(e) { 2. e.preventDefault(); 3. if (!navigator.geolocation) { 4. alert("Browser doesn't support geolocation"); 5. } else { 6. navigator.geolocation.getCurrentPosition(success, error); 7. } 8. } 9. // Get current position successfully 10. function success(position) { 11. var map, marker, 12. latitude = position.coords.latitude, 13. longitude = position.coords.longitude; 14. // Instance map using leaflet 15. map = L.map('map').setView([latitude, longitude], 13); 16. // Tile layer using key api at cloudmade.com 17. L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 18. key: '760506895e284217a7442ce2efe97797', 19. styleId: 103288, 20. maxZoom: 16 21. }).addTo(map); 22. // Marker using leaflet 23. marker = L.marker([latitude, longitude]).addTo(map); 24. // Popup in leaflet 25. marker.bindPopup('<p>Your location</p>').openPopup(); 26. } 27. // Get current position fail 28. function error() { 29. alert('Get current position fail. Please access codepen to get geolocation.'); 30. } 31.
1. html, body { 2. height: 100% 3. } 4. .map { 5. height: 300px; 6. } 7. .btn { 8. background-color: rgba(10, 10, 230, .5); 9. border: 0; 10. color: #fff; 11. padding: 10px; 12. text-shadow: 0 0 1px rgba(0, 0, 0, .3); 13. text-decoration: none; 14. margin: 0.5rem 0 1rem; 15. display: inline-block; 16. }
1. <html> 2. <head> 3. <meta charset="utf-8"> 4. <title>OpenStreetMap Example</title> 5. <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"> 6. <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> 7. </head> 8. <body> 9. <button class="btn" onclick="getLocation(event)">Click to show your location with OpenStreetMap</button> 10. <div id="map" class="map"></div> 11. </body> 12. </html>
1. <html> 2. <head> 3. <meta charset="utf-8"> 4. <title>OpenStreetMap Example</title> 5. <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"> 6. <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> 7. </head> 8. <body> 9. <button class="btn" onclick="getLocation(event)">Click to show your location with OpenStreetMap</button> 10. <div id="map" class="map"></div> 11. </body> 12. </html>
1. function getLocation(e) { 2. e.preventDefault(); 3. if (!navigator.geolocation) { 4. alert("Browser doesn't support geolocation"); 5. } else { 6. navigator.geolocation.getCurrentPosition(success, error); 7. } 8. } 9. // Get current position successfully 10. function success(position) { 11. var map, marker, 12. latitude = position.coords.latitude, 13. longitude = position.coords.longitude; 14. // Instance map using leaflet 15. map = L.map('map').setView([latitude, longitude], 13); 16. // Tile layer using key api at cloudmade.com 17. L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 18. key: '760506895e284217a7442ce2efe97797', 19. styleId: 103288, 20. maxZoom: 16 21. }).addTo(map); 22. // Marker using leaflet 23. marker = L.marker([latitude, longitude]).addTo(map); 24. // Popup in leaflet 25. marker.bindPopup('<p>Your location</p>').openPopup(); 26. } 27. // Get current position fail 28. function error() { 29. alert('Get current position fail. Please access codepen to get geolocation.'); 30. }
Different Web services can be used to get an address from longitude and latitude. Most are free of charge, but they will ask you to register an API key and enter your credit card number. If you send too many requests, you will be charged. Such a service is the Google Reverse Geocoding JavaScript API. For those of you who are really interested to know how this API works, please read the Google documentation and tutorials.
There is also an interesting Leaflet plugin (an extension to Leaflet) based on the Gisgraphy (free open source framework) service, that comes with a nice demo of reverse geocoding.
Let’s see some examples of use.
Example #1: how to get a physical address from the longitude and latitude.
Google reverse geocoding example (screenshot only):
Source code of this example (in order to run it, you need a Google API key, used at line 6.
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="utf-8"> 5. <title>Js bin </title> 6. <script src="https://maps.googleapis.com/maps/api/js?key= 7. PUT_HERE_YOUR_API_KEY&v=3.exp&sensor=false"> 8. </script><script> 9. // p elements for displaying lat / long and address 10. var displayCoords, myAddress; 11. // used with the google apis 12. var geocoder; 13. var map; 14. var infowindow = new google.maps.InfoWindow(); 15. var marker; 16. // Called when the page is loaded 17. 18. function init() { 19. displayCoords=document.getElementById("msg"); 20. myAddress = document.getElementById("address"); 21. geocoder = new google.maps.Geocoder(); 22. // In order to show something even before a user clicks on the button 23. var latlng = new google.maps.LatLng(34.0144, -6.83); 24. var mapOptions = { 25. zoom: 8, 26. center: latlng, 27. mapTypeId: 'roadmap' 28. } 29. map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions); 30. } // end of init() 31. // Called when the button is clicked 32. 33. function getLocation() { 34. if (navigator.geolocation) { 35. navigator.geolocation.getCurrentPosition(showPosition); 36. } else { 37. displayCoords.innerHTML="Geolocation API not supported by your browser."; 38. } 39. } 40. // Called when a position is available 41. 42. function showPosition(position) { 43. displayCoords.innerHTML="Latitude: " + position.coords.latitude + 44. "<br />Longitude: " + position.coords.longitude; 45. // Display the map 46. showOnGoogleMap(new google.maps.LatLng(position.coords.latitude, 47. position.coords.longitude)); 48. } 49. 50. function showOnGoogleMap(latlng) { 51. // Ask google geocoder for an address once we get a longitude and 52. // a latitude. In fact, the reverse geocoder sends back an array of "guesses" 53. // i.e. not just one address object, but several. Each entry in this array 54. // has several properties such as street, city, etc. We use the 55. "formatted_address" 56. // one here, but it might be interesting to get the detailed properties in other 57. // applications like a form with street, city, zip code etc. 58. geocoder.geocode({'latLng': latlng},reverseGeocoderSuccess); 59. 60. function reverseGeocoderSuccess(results, status) { 61. if (status == google.maps.GeocoderStatus.OK) { 62. if (results[1]) { 63. map.setZoom(11); 64. marker = new google.maps.Marker({ 65. position: latlng, 66. map: map 67. }); 68. infowindow.setContent(results[1].formatted_address); 69. infowindow.open(map, marker); 70. // Display address as text in the page 71. myAddress.innerHTML="Adress: " + results[0].formatted_address; 72. } else { 73. alert('No surface address found'); 74. } 75. } else { 76. alert('Geocoder failed due to: ' + status); 77. } 78. } // end of reverseGeocoderSuccess 79. } // end of showOnGoogleMap 80. </script> 81. </head> 82. <body onload="init()"> 83. <title>HTML5 + Geolocalisation + Google Maps API Reverse Geocoding</title> 84. <p id="msg">Click the button to get your coordinates:</p> 85. <p id="address"></p> 86. <button onclick="getLocation()">Where am I ?</button> 87. <div id="map_canvas" style="width: 500px; height: 300px"></div> 88. </body> 89. </html>
Gisgraphy (free service) reverse geocoding example (screenshot only, click on it to see the demo on the Gisgraphy website):
Important note: these examples below rely on an external GitHub resource.
Please, pan and zoom on the map and click.
The longitude and latitude are computed from your click and a free reverse geocoding service is used to convert to a physical address.
1. <!DOCTYPE html> 2. <html> 3. <head> 4. <title>Leaflet Control Geocoder</title> 5. <meta charset="utf-8" /> 6. <meta 7. name="viewport" 8. content="width=device-width, user-scalable=no initial-scale=1, maximum-scale=1" 9. /> 10. <link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css 11. " /> 12. <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" /> 13. <script src="https://unpkg.com/leaflet@latest/dist/leaflet-src.js"></script> 14. <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script> 15. <style type="text/css"> 16. body { 17. margin: 0; 18. } 19. #map { 20. position: absolute; 21. width: 100%; 22. height: 100%; 23. } 24. </style> 25.</head> 26.<body> 27. <div id="map"></div> 28. <script type="text/javascript"> 29. var map = L.map('map').setView([0, 0], 2); 30. var geocoder = L.Control.Geocoder.nominatim(); 31. if (URLSearchParams && location.search) { 32. // parse /?geocoder=nominatim from URL 33. var params = new URLSearchParams(location.search); 34. var geocoderString = params.get('geocoder'); 35. if (geocoderString && L.Control.Geocoder[geocoderString]) { 36. console.log('Using geocoder', geocoderString); 37. geocoder = L.Control.Geocoder[geocoderString](); 38. } else if (geocoderString) { 39. console.warn('Unsupported geocoder', geocoderString); 40. } 41. } 42. var control = L.Control.geocoder({ 43. query: 'Moon', 44. placeholder: 'Search here...', 45. geocoder: geocoder 46. }).addTo(map); 47. var marker; 48. setTimeout(function() { 49. control.setQuery('Earth'); 50. }, 12000); 51. L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', { 52. attribution: '© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors' 53. }).addTo(map); 54. map.on('click', function(e) { 55. console.log(e.latlng) 56. geocoder.reverse(e.latlng, map.options.crs.scale(map.getZoom()), function(results) { 57. var r = results[0]; 58. if (r) { 59. if (marker) { 60. marker 61. .setLatLng(r.center) 62. .setPopupContent(r.html || r.name) 63. .openPopup(); 64. } else { 65. marker = L.marker(r.center) 66. .bindPopup(r.name) 67. .addTo(map) 68. .openPopup(); 69. } 70. } 71. }); 72. }); 73. </script> 74.</body> 75.</html>
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <title>Leaflet Control Geocoder</title>
5. <meta charset="utf-8" />
6. <meta
7. name="viewport"
8. content="width=device-width, user-scalable=no initial-scale=1, maximum-scale=1"
9. />
10. <link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css" />
11. <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
12. <script src="https://unpkg.com/leaflet@latest/dist/leaflet-src.js"></script>
13. <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
14. <style type="text/css">
15. body {
16. margin: 0;
17. }
18. #map {
19. position: absolute;
20. width: 100%;
21. height: 100%;
22. }
23. </style>
24. </head>
25. <body>
26. <div id="map"></div>
27. <script type="text/javascript">
28. var geocoder = L.Control.Geocoder.nominatim();
29. var map, marker, latitude, longitude;
30. function getLocation() {
31. if (!navigator.geolocation) {
32. alert("Browser doesn't support geolocation");
33. } else {
34. navigator.geolocation.getCurrentPosition(success, error);
35. }
36. }
37. // Get current position successfully
38. function success(position) {
39. latitude = position.coords.latitude;
40. longitude = position.coords.longitude;
41. // Instance map using leaflet
42. map = L.map('map').setView([latitude, longitude], 13);
43. // Tile layer using key api at cloudmade.com
44. L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
45. key: '760506895e284217a7442ce2efe97797',
46. styleId: 103288,
47. maxZoom: 16
48. }).addTo(map);
49. // Marker using leaflet
50. marker = L.marker([latitude, longitude]).addTo(map);
51. // Popup in leaflet
52. marker.bindPopup('<p></p>').openPopup();
53. getPhysicalAddress({lat:latitude, lng:longitude});
54. }
55. // Get current position fail
56. function error() {
57. alert('Get current position fail. Please access codepen to get geolocation.');
58. }
59. var marker;
60. function getPhysicalAddress(latlong) {
61. geocoder.reverse(latlong, map.options.crs.scale(map.getZoom()), function(results) {
62. var r = results[0];
63. if (r) {
64. if (marker) {
65. marker
66. .setLatLng(r.center)
67. .setPopupContent(r.html || r.name)
68. .openPopup();
69. } else {
70. marker = L.marker(r.center)
71. .bindPopup(r.name)
72. .addTo(map)
73. .openPopup();
74. }
75. }
76. });
77. }
78. getLocation();
79. </script>
80. </body>
81. </html>
This is just a variation of the previous examples. We embedded the interactive map in a form, and we display the results of the reverse geocoder in a form field. This example might be useful if you want to pre-fill the address of a registration form, depending on the current location of the person who is registering.
Click on the Codepen logo (on the top right) so to run the online example (for security reasons the embedded version cannot run in this page):
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <title>Gimmee My Location</title>
5. <meta charset="utf-8" />
6. <meta name="viewport"
7. content="width=device-width, user-scalable=no initial-scale=1, maximum-scale=1" />
8. <link rel="stylesheet" href="https://unpkg.com/leaflet@latest/dist/leaflet.css" />
9. <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
10. <script src="https://unpkg.com/leaflet@latest/dist/leaflet-src.js"></script>
11. <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
12. </head>
13. <body onload="getLocation();">
14. <h1>Example of the use of reverse geocoder in a form</h1>
15. <form>
16. <fieldset>
17. <legend>Form example with map and address...</legend>
18. <div id="map" style="width: 500px; height: 300px"></div>
19. </fieldset>
20. <fieldset>
21. <legend>Surface address</legend>
22. <input id="surfaceAddress" size=110 type="text">
23. <fieldset>
24. <form>
25. <div id="map"></div>
26.
27. <script type="text/javascript">
28. var geocoder = L.Control.Geocoder.nominatim();
29. var map, marker, latitude, longitude;
30. function getLocation() {
31. if (!navigator.geolocation) {
32. alert("Browser doesn't support geolocation");
33. } else {
34. navigator.geolocation.getCurrentPosition(success, error);
35. }
36. }
37. // Get current position successfully
38. function success(position) {
39. latitude = position.coords.latitude;
40. longitude = position.coords.longitude;
41. // Instance map using leaflet
42. map = L.map('map').setView([latitude, longitude], 13);
43. // Tile layer using key api at cloudmade.com
44. L.tileLayer('https://{s}.tile.openstreetmap.org/{z}{x}{y}.png', {
45. key: '760506895e284217a7442ce2efe97797',
46. styleId: 103288,
47. maxZoom: 16
48. }).addTo(map);
49. // Marker using leaflet
50. marker = L.marker([latitude, longitude]).addTo(map);
51. // Popup in leaflet
52. marker.bindPopup('<p></p>').openPopup();
53. getPhysicalAddress({lat:latitude, lng:longitude});
54. }
55. // Get current position fail
56. function error() {
57. alert('Get current position fail. Please access codepen to get geolocation.');
58. }
59. var marker;
60. function getPhysicalAddress(latlong) {
61. geocoder.reverse(latlong, 500000, function(results) {
62. var r = results[0];
63. console.log(r.name);
64. document.querySelector("#surfaceAddress").value = r.name;
65. if (r) {
66. if (marker) {
67. marker
68. .setLatLng(r.center)
69. .setPopupContent(r.html || r.name)
70. .openPopup();
71. } else {
72. marker = L.marker(r.center)
73. .bindPopup(r.name)
74. .addTo(map)
75. .openPopup();
76. }
77. }
78. });
79. }
80. </script>
81. </body>
82. </html>
Here are a few project ideas. Your classmates and the team who prepared the course will be glad to try them and offer feedback. Please post URLs in this discussion forum. These projects are optional, meaning that they won’t be graded. However, it is important to complete them to ensure good understanding of the material.