13.1 Observers
# Observers
The Observer APIs allow us to create objects that watch selected parts of the DOM. If changes to the DOM elements we can automatically have a callback function run.
# Resize Observer
The resize observer
can be used to duplicate the same functionality as a Media Query, but the real power is being able to watch specific DOM elements instead of the whole page and see if the desired
element has changed to meet a dynamic size criteria, then we can do anything we want with JavaScript. We can add or remove elements from the page. We can fetch new content. We can apply new CSS.
We can rearrange our page layout entirely.
The basic script works like this:
//create an observer passing in a callback function
let observer = new ResizeObserver(handleResize);
//tell the observer what to watch
observer.observe(document.querySelector('.something'));
//create your callback function
function handleResize(entries) {
//function will be sent an array of elements being observed
//each entry has a `target` property that points to the observed element
let myelement = entries[0].target;
myelement.className.add('hasChanged');
//each entry also has a `contentRect` object with width and height properties
console.log(entries[0].contentRect.width);
console.log(entries[0].contentRect.height);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
And here is a video that explains the whole process.
MDN reference for Resize Observer (opens new window)
# Intersection Observer
The intersection observer
is a very common observer. It is used to create effects on the page as the user scrolls. When an observed element intersects with an area of the screen, it triggers the
callback and lets you run your script. Generally, the script will do something like add a css class to trigger a transition or animation. However, it could also do something like fetch new content
from a remote API or the Cache.
The intersection observers work in a similar way to the resize observer. You create the observer and then tell it what to watch. There will be a callback function that runs when the intersections occur. The callback function will be pass the array of elements being observed with properties about each that you can use in deciding what you want to do.
The intersection observers need some extra options that you define when creating it. The first option is called root
, and it defines the viewport that will be used to watch for intersections with
the observed element. The second is rootMargin
and lets you expand or shrink your viewport when watching for intersections. The final option is threshold
and let's you define a percentage of how
much of the observed element must be intersecting with the viewport before calling the callback function.
//set up the options
let opts = {
root: null, //null means the whole screen. Otherwise it can be another element as the viewport
rootMargin: '0px -50px', //top-bottom and left-right values.
//positive means bigger than viewport. negative means inset from edges
threshold: 0.5, //percentage of observed element inside defined area. 0.5 == 50%
};
//create the observer with the options and callback function
let observer = new IntersectionObserver(handleIntersect, opts);
//tell it what to observe.
observer.observe(document.querySelector('.somediv'));
2
3
4
5
6
7
8
9
10
11
If you want to observe many elements then just call observe on each.
There is also an unobserve
method that lets you remove an element from the set being observed.
function handleIntersect(entries) {
entries.forEach((entry) => {
//for each observed item report if it is currently intersecting
console.log(entry.isIntersecting); //boolean value
//use an if statement to do whatever you like
});
}
2
3
4
5
6
7
First video is a basic introduction to building an intersection observer and demonstrates effects on paragraphs as the user scrolls.
The second video shows how you could build an infinite scrolling system that dynamically loads new content as the user scrolls.
MDN reference for Intersection Observer (opens new window)
# Mutation Observer
The Mutation Observer
will let you observe DOM elements and watch for changes to their textContent
or attributes
or children
. It can be a useful observer to do things like highlight areas of
the page when new content is added, changed, or removed.
Similar to the Intersection Observer, the Mutation Observer needs a set of options.
//set the options
const opts = {
attributes: true, //report if attributes are changed
attributeFilter: ['src', 'href'], //optional list of attributes to watch
attributeOldValue: false; //optional. if true old value will be saved for callback function
childList: true, //report if children are changed
characterData: false, //optional. if true, will save the text for the callback function
characterDataOldValue: false; //optional. if true old text value will be saved for callback function
subtree: false, //report if elements further down in the descendent tree are changed
//this last one, pluse the characterData ones can come with a performance hit.
};
//create the observer object with callback function and options
let observer = new MutationObserver(handleMutation, opts);
//add the element(s) you want to watch
observer.observe(document.querySelector('.somediv'));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
You can add more elements by calling observe()
again to add other elements to the observed set. You can always call unobserve()
to remove elements from the observed set.
function handleMutation(mutations) {
//entries is the list of _MUTATED_ observed elements
//each will have a `type` property that indicates which type of mutation it is
switch (mutations[0].type) {
case 'childList':
//a child element was mutated
console.log(mutations[0].target);
//old and new values might be available if set in options
break;
case 'attributes':
//attribute was changed
console.log(mutations[0].target);
//we can find out which attribute was mutated
console.log(mutations[0].attributeName);
//plus the old and new values if set in options
break;
default:
//subTree mutation
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MDN reference for Mutation Observer (opens new window)
# JS Classes
JavaScript does not have actual "classes" like languages such as Swift, Kotlin, C++, C#, etc. JavaScript uses prototypes to define how object properties and methods are shared through inheritance.
However, because of 20 years of confusion over how the keyword this
works, a desire to standardize the syntax for creating new objects, plus due to the number of developers coming to JavaScript from
other languages in the last 10 years, a class
keyword was added. A syntactic sugar was added to JavaScript that lets developers create objects with a class-like syntax.
If you want to create an Object in JavaScript there are a number of ways that you can do this.
//an object literal
//just write what you want as the props and use the default values for all property descriptors
let objLiteral = { id: 123, name: 'Steve' };
//internally, this will call new Object()
//use the Object constructor and pass it an object literal
let objInstance = new Object({ id: 123, name: 'Steve' });
//Object.create method
//pass in a prototype object reference and a properties object
let createdObj = Object.create(somePrototypeObjectReference, {
id: { value: 123, enumerable: true },
name: { value: 'Steve', enumerable: true },
});
//A constructor function
//when calling a function with `new` the return value will be your new object
//CANNOT do this with an ARROW function
function myBuilder() {
this.id = 123; //add properties to the object that will be returned
this.name = 'Steve';
}
let constructedObjInstance = new myBuilder();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
You can then extend the functionality of any object by using the prototype
keyword to add methods to the prototype
object. The prototype
is the place to put methods that will be shared by all instances of the object type.
//to add something to a prototype, we use the prototype property
//when we have access to the constructor function itself
myBuilder.prototype.someNewMethod = function () {};
//with the object instances we need to get to the constructor object to find the prototype object.
//the object instances are the objects that are built by the constructor function.
//Two ways to access the prototype property.
//1. with the `constructor` property
objLiteral.constructor.prototype.someNewMethod = function () {};
createdObj.constructor.constructor.prototype.someNewMethod = function () {};
objInstance.constructor.prototype.someNewMethod = function () {};
constructedObjInstance.constructor.prototype.someNewMethod = function () {};
// 2. by using the __proto__ as the shortcut for `constructor.prototype`
//this is a non-standard way to do this
objLiteral.__proto__.someNewMethod = function () {};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
When ES6 was launched it added the class
keyword to JavaScript as a syntactic sugar. This was an attempt to standardize (yet again) the way that objects are created in JavaScript and to make the
language appear more familiar to the many developers migrating to JavaScript from other languages.
This does not mean that JavaScript has classes like object oriented languages, such as Java, do. JavaScript STILL uses prototype
when creating the inheritance for objects. This is just a different way to write the code to extend and connect objects and their prototypes.
class myObjType {
constructor() {
this.id = 123;
this.name = 'Steve';
}
someNewMethod() {
//this method is added to the prototype
//note the shorthand syntax for defining the function without `function`
}
}
//create one of your objects, just like with a function plus `new`
let myClassObj = new myObjType(); //calls the constructor() function
//myClassObj will have the two properties - id and name
//the prototype for myClassObj's constructor will hold the `someNewMethod` function
2
3
4
5
6
7
8
9
10
11
12
13
14
15
By using the class
keyword we can build their constructors, define their properties and define their prototype methods in a more predictable way. This does not stop JS using the prototype
chain or
change how anything happens internally.
You will still use the Object literal syntax for 80%+ of what you do with objects in JavaScript. The class syntax just gives you an alternative standard to follow when things become more complex.
# What to do this week
TODO Things to do before next week.
- Read all the content from
Modules 13.1, 13.2, and 14.1
.