Chapter 4: Styling and Animations
Visual effects in JavaScript can significantly enhance user experience. jQuery simplifies adding dynamic styles and creating complex animations with minimal code. These effects not only improve aesthetics but also provide usability benefits, especially in Ajax applications where users need to track changes.
Dynamic CSS Modifications
Before diving into jQuery effects, let's review CSS manipulation. Previously, we modified styles by adding/removing classes. However, sometimes we need inline styles not defined in external stylesheets. jQuery's .css() method handles this.
The .css() method works as both a getter and setter. To retrieve a single property value, pass the property name as a string. For multiple properties, pass an array of property names to get an object of property-value pairs.
// Get a single property value
.css('property')
// "value"
// Get multiple properties' values
.css(['property1', 'property-2'])
// {"property1": "value1", "property-2": "value2"}
For setting styles, .css() has two forms: single property-value or an object of property-value pairs.
// Single property and its value
.css('property', 'value')
// Object of property-value pairs
.css({
property1: 'value1',
'property-2': 'value2'
})
These key-value collections are called object literals, representing JavaScript objects.
Object Literal Notation
In property values, strings are quoted, but other data types like numbers don't need quotes. Property names are strings, so they're usually quoted. However, if the property name is a valid JavaScript identifier (like camelCase), quotes aren't needed.
We use .css() similarly to .addClass(), applying it to a jQuery object referencing DOM elements. Let's demonstrate with a style switcher:
<div id="switcher">
<div class="label">Text Size</div>
<button id="switcher-default">Default</button>
<button id="switcher-large">Bigger</button>
<button id="switcher-small">Smaller</button>
</div>
<div class="speech">
<p>Fourscore and seven years ago our fathers brought forth
on this continent a new nation, conceived in liberty,
and dedicated to the proposition that all men are created
equal.</p>
...
</div>
After linking a stylesheet with basic rules, the page initially looks like this:
When we complete our code, clicking "Bigger" and "Smaller" buttons will increase or decrease the text size of <div class="speech">, while "Default" resets it.
Setting Computed Style Values
If we only need to change font size once, .addClass() works. But for incremental changes, we calculate new sizes by getting the current size and increasing by a factor (e.g., 40%).
$(() => {
const $textBlock = $('div.speech');
$('#switcher-large')
.click(() => {
const currentSize = parseFloat($textBlock.css('fontSize'));
$textBlock.css('fontSize', `${currentSize * 1.4}px`);
});
});
The first line creates a constant $textBlock holding the jQuery object for <div class="speech">. The dollar sign reminds us it's a jQuery object. Using const prevents accidental reassignment.
Inside the click handler, parseFloat() extracts the numeric font size. For the "Smaller" button, we use division: currentSize / 1.4. Better yet, combine both in a single handler:
$(() => {
const sizeMap = {
'switcher-small': n => n / 1.4,
'switcher-large': n => n * 1.4
};
const $textBlock = $('div.speech');
$('#switcher button')
.click((e) => {
const currentSize = parseFloat($textBlock.css('fontSize'));
$textBlock.css(
'fontSize',
`${sizeMap[e.target.id]}px`
);
});
});
e.target.id determines the action. sizeMap stores behaviors, making it easier to add/remove than if statements.
To reset to default, store the initial size:
$(() => {
const sizeMap = {
'switcher-small': n => n / 1.4,
'switcher-large': n => n * 1.4,
'switcher-default': () => defaultSize
};
const $textBlock = $('div.speech');
const defaultSize = parseFloat($textBlock.css('fontSize'));
$('#switcher button')
.click((e) => {
const currentSize = parseFloat($textBlock.css('fontSize'));
$textBlock.css(
'fontSize',
`${sizeMap[e.target.id]}px`
);
});
});
Vendor-Specific Styles
When browsers introduce experimental properties, they use prefixes (e.g., -webkit-). jQuery handles this by trying standard names first, then checking prefixes.
Chapter 5: DOM Manipulation
Web experiences involve server-client collaboration. Traditionally, servers generate HTML, but JavaScript lets us modify the document directly. This chapter covers:
- Modifying the document via DOM interfaces
- Creating elements and text
- Moving or removing elements
- Transforming documents by adding/removing/modifying attributes
Manipulating Attributes and Properties
Previously, we used .addClass() and .removeClass() to change element appearance. These methods modify the className DOM property, not the class attribute. jQuery handles class manipulation efficiently.
Non-Class Attributes
For attributes like id, rel, or href, use .attr() and .removeAttr(). These methods simplify attribute changes and support multiple attributes at once.
$(() => {
$('div.chapter a')
.attr({
rel: 'external',
title: 'Learn more at Wikipedia'
});
});
Value Callbacks
For unique values (like id), use value callbacks. These functions are called for each element, returning a new value.
$(() => {
$('div.chapter a[href*="wikipedia"]')
.attr({
rel: 'external',
title: function() {
return `Learn more about ${$(this).text()} at Wikipedia.`;
},
id: index => `wikilink-${index}`
});
});
Data Attributes
HTML5 data attributes store application-specific data. jQuery's .data() method reads/writes these values.
$(() => {
$('#hide-read')
.change((e) => {
if ($(e.target).is(':checked')) {
$('.chapter p')
.filter((i, p) => $(p).data('read'))
.hide();
} else {
$('.chapter p').show();
}
});
$('.chapter p')
.click((e) => {
const $elm = $(e.target);
$elm
.css(
'textDecoration',
$elm.data('read') ? 'none' : 'line-through'
)
.data('read', !$(e.target).data('read'));
});
});
DOM Tree Operations
The $() function creates new elements from HTML strings. For example:
$(() => {
$('<a href="#top">back to top</a>')
.insertAfter('div.chapter p');
$('<a id="top"></a>')
.prependTo('body');
});
These elements aren't in the document yet. Use insertion methods to place them:
.insertAfter(): Outside and after.prependTo(): Inside and before.appendTo(): Inside and after.insertBefore(): Outside and before
Wrapping Elements
Use wrapping methods to enclose elements. For footnotes:
$(() => {
const $notes = $('<ol id="notes"></ol>')
.insertBefore('#footer');
$('span.footnote')
.each((i, span) => {
$(span)
.before(`<sup>${i + 1}</sup>`)
.appendTo($notes)
.wrap('<li></li>');
});
});
Cloning Elements
Use .clone() to duplicate elements. For pull quotes:
$(() => {
$('span.pull-quote')
.each((i, span) => {
$(span)
.parent()
.css('position', 'relative');
});
$('span.pull-quote')
.each((i, span) => {
$(span)
.clone()
.addClass('pulled')
.find('span.drop')
.html('…')
.end()
.text((i, text) => text)
.prependTo(
$(span)
.parent()
.css('position', 'relative')
);
});
});
Chapter 6: Using Ajax
Ajax (Asynchronous JavaScript and XML) enables loading data without page refreshes. This chapter covers:
- Loading data without refreshing
- Sending data to the server
- Handling HTML, XML, and JSON
- Providing user feedback
Loading Data on Demand
Let's build a page displaying dictionary entries grouped by starting letter. The HTML structure:
<div id="dictionary">
</div>
<div class="letters">
<div class="letter" id="letter-a">
<h3><a href="entries-a.html">A</a></h3>
</div>
<div class="letter" id="letter-b">
<h3><a href="entries-a.html">B</a></h3>
</div>
<div class="letter" id="letter-c">
<h3><a href="entries-a.html">C</a></h3>
</div>
<div class="letter" id="letter-d">
<h3><a href="entries-a.html">D</a></h3>
</div>
<!-- and so on -->
</div>
Appending HTML
Load HTML fragments with .load():
$(() => {
$('#letter-a a')
.click((e) => {
e.preventDefault();
$('#dictionary').load('a.html');
});
});
Handling JSON
For structured data, use $.getJSON():
$(() => {
$('#letter-b a')
.click((e) => {
e.preventDefault();
$.getJSON('b.json', (data) => {
const html = data.reduce((result, entry) => `
${result}
<div class="entry">
<h3 class="term">${entry.term}</h3>
<div class="part">${entry.part}</div>
<div class="definition">
${entry.definition}
</div>
</div>
`, '');
$('#dictionary').html(html);
});
});
});
Loading XML
For XML data, use $.get() with DOM traversal:
$(() => {
$('#letter-d a')
.click((e) => {
e.preventDefault();
$.get('d.xml', (data) => {
const html = $(data)
.find('entry')
.get()
.reduce((result, entry) => `
${result}
<div class="entry">
<h3 class="term">${$(entry).attr('term')}</h3>
<div class="part">${$(entry).attr('part')}</div>
<div class="definition">
${$(entry).find('definition').text()}
</div>
</div>
`, '');
$('#dictionary').html(html);
});
});
});
Sending Data to the Server
Use $.get() or $.post() to send data. For form submissions, .serialize() simplifies data preparation:
$(() => {
$('#letter-f form')
.submit((e) => {
e.preventDefault();
$.post(
$(e.target).attr('action'),
$(e.target).serialize(),
(data) => { $('#dictionary').html(data); }
);
});
});
Handling Errors
Use .fail() for error handling:
$(() => {
$('#letter-e a')
.click((e) => {
e.preventDefault();
$.get('notfound', { term: $(e.target).text() }, (data) => {
$('#dictionary').html(data);
}).fail((xhr) => {
$('#dictionary').html(`Error: ${xhr.status}`);
});
});
});
Ajax and Events
Use event delegation for dynamically added elements:
$(() => {
$('body')
.on('click', 'h3.term', (e) => {
$(e.target)
.siblings('.definition')
.slideToggle();
});
});