JavaScript 30 Day Challenge is created by @wesbos
Check out Wes' course at https://JavaScript30.com
The idea is to build 30 projects with vanilla Javascript! No compiler, frameworks, or external libraries.
I will be keeping track of the various JS & CSS concepts learned in my branch study-notes
. The original Javascript30 starter file are kept on the master
branch!
The data-*
global attributes form a class of attributes called custom data attributes, that allow proprietary information to be exchanged between the HTML and its DOM representation by scripts.
The *
may be replaced by any name following the production rule of xml names with the following restrictions:
- the name must not start with xml, whatever case is used for these letters;
- the name must not contain any semicolon (U+003A);
- the name must not contain capital A to Z letters.
Data attribute values can be accessible via:
HTMLElement.dataset.attributeName
OR
HTMLElement.dataset[attributeName]
OR
HTMLElement[data-key="attributeName"]
Window
is the first thing that gets loaded into the browserDocument
object is your html, aspx, php, or other document that will be loaded into the browser
To access a property for the window it is window.property, if it is document
it is window.document.property
which is also available in short as document.property
.
- An iframe actually is considered as a new window with its own document loaded into it To access a frame is window.frames[], which is an array of all the frames. If you only have one iframe you access it by using window.frames[0].
Reference: Window object properties Document object properties
Using classList
is a convenient way to access an element's list of classes (class names), with 6 methods available:
add(str)
- add specified class values. If these classes already exist in attribute of the element, then they are ignored.remove(str)
- remove specified class values. Removing a class that does not exist, does NOT throw an error.item(num)
- Return class value by index numbertoggle( String [, force] )
When only one argument is present: Toggle class value; (if class exists then remove it and return false, if not, add class and return true)
When a second argument is present: If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it.
contains(str)
- checks if specified class value existsreplace(oldClass, newClass)
- Replaces an existing class with a new class.
Sets a timer which executes a function or specified piece of code once after the timer expires.
-
Wes noted that if you are using the
transition
property in CSS, usingsetTimeout
in your JS may result in transitions becoming out of sync. For this reason, Wes attaches a listener on thetransitionend
event -
A timeout may also take longer to fire than anticipated, caused by browser throttling and code execution sequence on single thread apps. Read more about these potential delays here.
The transitionend
event is fired when a CSS transition has completed. In the case where a transition is removed before completion, such as if the transition-property
is removed or display
is set to "none", then the event will not be generated.
The animationend event is fired when a CSS animation has completed.
In the case where an animation is removed before completion, such as if the animation-duration
is removed or display
is set to "none", then the event will not be generated.
CSS Syntax: transform: none|transform-functions|initial|inherit;
The transform
property applies a 2D or 3D transformation to an element. This property allows you to rotate, scale, move, skew, etc., elements
rotate(angle)
- Defines a 2D rotation, the angle is specified in the parameter
CSS syntax: transform-origin: x-axis y-axis z-axis|initial|inherit;
The transform-origin
property allows you to change the position of transformed elements.
2D transformations can change the x- and y-axis of an element. 3D transformations can also change the z-axis of an element.
The default value is: transform-origin: 50% 550% 0
- Wes noted that the default value for
transform-origin
is 50% on the x-axis. To adjust the rotation pivot point on the most right of the clock hands, we can define:transform-origin: 100%
ortransform-origin: right
CSS syntax: transition: property duration timing-function delay|initial|inherit;
CSS transitions allows you to change property values smoothly (from one value to another), over a given duration.
To create a transition effect, you must specify two things:
transition-property
- CSS property you want to add an effect totransition-duration
- Specifies how many seconds or milliseconds the transition effect takes to complete
The transition-timing-function
property specifies the speed curve of the transition effect.
This property allows a transition effect to change speed over its duration.
- In console, we can also click on the Open Cubic Bezier Editor icon next to the transition function to quickly edit the transition effect, then simply copy and paste the values into our CSS.
e.g. transition-timing-function: cubic-bezier(0.1, 2.74, 1, 0.48)
CSS variables are entities defined by CSS authors that contain specific values to be reused throughout a document.
CSS variables allow a value to be stored in one place, then referenced in multiple other places. An additional benefit is semantic identifiers.
For example --main-text-color
is easier to understand than #00ff00
, especially if this same color is also used in another context.
They are set using custom property notation (e.g. --main-color: black;
) and are accessed using the var()
function (e.g., color: var(--main-color);
)
CSS variables can be updated with Javascript. Whereas with SASS variables, variables are defined at compile time and cannot be changed after.
NodeList
objects are collections of nodes such as those returned by document.querySelectorAll()
method. NodeList
can be:
- Live collection: changes in the DOM are reflected in the collection (e.g.
Node.childNodes
) - Static collection: any subsequent change in the DOM does not affect the content of the collection. (e.g.
document.querySelectorAll()
)
An Array
has a lot more methods than a NodeList
, such as map(), reduce()
. We can convert a NodeList into an array:
let x = querySelectorAll(selector)
Array.prototype.slice.call(x);
Or use Array.from()
:
let x = querySelectorAll(selector)
Array.from(x);
The HTMLElement.dataset
property allows access, both in reading and writing mode, to all the custom data attributes (data-*
) set on the element, either in HTML or in the DOM.
dataset
property itself can be read, but not directly written- all writes must be to
dataset.property
, which in turn represent the data attributes.
The filter()
method calls a provided callback
function once for each element in an array, and constructs a new array of all the values for which callback returns a value that returns true
or truthy
This function takes one mandatory argument data
, which must be an array or an object, and one additional optional parameter columns
.
It logs data
as a table. Each element in the array (or enumerable property if data
is an object) will be a row in the table.
Syntax: arr.sort([compareFunction])
The sort()
method sorts the elements of an array in place and returns the array, using a compareFunction
to determine the sort order. Array elements are sorted according to the return value of the compareFunction
:
- If
compareFunction(a, b)
< 0, sorta
to an index lower thanb
, i.e.a
comes first - If
compareFunction(a, b)
= 0, leavea
andb
unchanged with respect to each other, but sorted with respect to all different elements - If
compareFunction(a, b)
> 0, sortb
to an index lower thana
, i.e.b
comes first.
Example:
function compare(a, b) {
if (a is less than b by some ordering criterion) {
return -1;
}
if (a is greater than b by the ordering criterion) {
return 1;
}
// a must be equal to b
return 0;
}
Syntax: arr.reduce(callback[, initialValue])
The reduce()
method applies a function against an accumulator
(like a running total) and each element in the array (from left to right) to reduce it to a single value.
Example:
var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
// sum is 6
The Node.textContent
property represents the text content of a node and its descendants.
Internet Explorer introduced node.innerText
. The intention is similar but with the following differences:
- While
textContent
gets the content of all elements, including<script>
and<style>
elements,innerText
does not. innerText
is aware of style and will not return the text of hidden elements, whereastextContent
will.
Quite often, in order to retrieve or write text within an element, people use innerHTML
. Element.innerHTML
returns the HTML as its name indicates. However, textContent
often has better performance because the text is not parsed as HTML, and can prevent XSS attacks.
The includes()
method determines whether one string may be found within another string, returning true
or false
as appropriate.
The indexOf()
method returns the index within the calling String
object of the first occurrence of the specified value, starting the search at fromIndex. Returns -1 if the value is not found.
Flexible Box Layout Module, makes it easier to design flexible responsive layout structure without using float or positioning. The key feature of flexbox is the fact that items in a flex layout can grow and shrink. Space can be assigned to the items themselves, or distributed between or around the items.
Learn more about Flexbox with Wes through his Flexbox course - a 20 video course series!
A flexbox layout is defined using display:flex
or display:inline-flex
on the parent item (e.g. div
). This element then becomes a flex container
, and each one of its children becomes a flex item
.
An element can be both a flex container
and a flex item
!
The flex
CSS property specifies how a flex item will grow or shrink so as to fit the space available in its flex container. This is a shorthand property that sets flex-grow
, flex-shrink
, and flex-basis
.
Specifies the initial size of the flex item, before any available space is distributed according to the flex factors.
The Fetch API provides an interface for fetching resources (including across the network). It will seem familiar to anyone who has used XMLHttpRequest
, but the new API provides a more powerful and flexible feature set.
Fetch
provides a generic definition of Request
and Response
objects.
The fetch()
method takes one mandatory argument, the path to the resource you want to fetch. It returns a Promise
that resolves to the Response
to that request, whether it is successful or not. You can also optionally pass in an init
options object as the second argument.
Once a Response
is retrieved, there are a number of methods available to define what the body content is and how it should be handled.
A Promise
is a proxy for a value not necessarily known when the promise is created. It is an object which represents the eventual completion (or failure) of an asynchronous operation and resulting value.
A promise allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a Promise
to supply the value at some point in the future.
A Promise
is in one of these states:
pending
: initial state, neither fulfilled nor rejected.fulfilled
: meaning that the operation completed successfully.rejected
: meaning that the operation failed.
Instead of passing callbacks into a function, we can attach callback functions to a Promise
object - an asynchronous function call.
Example:
const promise = doSomething();
promise.then(successCallback, failureCallback);
Or:
doSomething().then(successCallback, failureCallback);
- Callbacks attached to a
Promise
will never be called before the completion of the current run of the JavaScript event loop. - Multiple callbacks may be added by calling
.then
several times, to be executed independently in insertion order.
Read more about using promises here
The Response
interface of the Fetch API
represents the response to a request, which has many properties and methods. For example:
Response.ok
: Contains a boolean stating whether the response was successful (status in the range 200-299) or notResponse.statusText
: Contains the status message corresponding to the status code (e.g., OK for 200).Response.body
: the body of the response/request, allowing you to declare what its content type is and how it should be handled.
Response.clone()
: Creates a clone of a Response object.Body.json()
: Takes aResponse
stream and reads it to completion. It returns a promise that resolves with the result of parsing the body text as JSON.Body.formData()
: Takes aResponse
stream and reads it to completion. It returns a promise that resolves with a FormData object.
In Wes's video, he named the returned value of fetch(endpoint)
as a blob
. Per MDN definition:
A Blob
object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format.
Spread syntax (...)
allows an iterable such as an array or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
The RegExp
constructor creates a regular expression object for matching text with a pattern.
Learn more about using Regular Expressions here
The match()
method retrieves the matches when matching a string against a regular expression.
The replace()
method returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp
, and the replacement can be a string or a function to be called for each match.
The getFullYear()
method returns the year of the specified date according to local time. Use this method instead of the getYear()
method - as it has been deprecated!
Syntax: arr.some(callback[, thisArg])
The some()
method tests whether at least one element in the array passes the test implemented by the provided callback function.
some()
executes the callback function once for each element present in the array until it finds one where callback returns a truthy
value.
If such an element is found, some()
immediately returns true
. Otherwise, some()
returns false
.
Syntax: arr.every(callback[, thisArg])
The every()
method tests whether all elements in the array pass the test implemented by the provided callback function.
The every()
method executes the provided callback function once for each element present in the array until it finds one where callback returns a falsy
value. If such an element is found, the every()
immediately returns false
. Otherwise, every()
returns true
.
Syntax: arr.find(callback[, thisArg])
The find()
method returns the value of the first element in the array that satisfies the provided callback function. Otherwise undefined
is returned.
Syntax: arr.findIndex(callback[, thisArg])
The findIndex()
method returns the index of the first element in the array that satisfies the provided callback function. If the callback never returns a truthy
value or the array's length is 0, findIndex()
returns -1.
splice()
method changes the contents of an array by removing existing elements and/or adding new elements. It will return an array containing the deleted elements.
Syntax: array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
Example:
# Remove 0 element, insert 'drum'
var myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];
var removed = myFish.splice(2, 0, 'drum');
// myFish is ["angel", "clown", "drum", "mandarin", "sturgeon"]
// removed is [], no elements removed
# Remove 1 element from index 3
var myFish = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon'];
var removed = myFish.splice(3, 1);
// removed is ["mandarin"]
// myFish is ["angel", "clown", "drum", "sturgeon"]
slice()
method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
Syntax: arr.slice([begin[, end]])
Example:
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3);
// fruits contains ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
// citrus contains ['Orange','Lemon']
-
window.innerWidth()
: Width (in pixels) of the browser window viewport including, if rendered, the vertical scrollbar. -
window.innerHeight()
: Height (in pixels) of the browser window viewport including, if rendered, the horizontal scrollbar.
The HTML <canvas>
element provides an empty graphic zone on which specific JavaScript APIs can draw (such as Canvas 2D or WebGL). This can, for instance, be used to draw graphs, make photo composition or animations.
<canvas>
element has only two attributes, width
and height
. These are both optional and can also be set using DOM properties. When no width and height attributes are specified, the canvas will initially be 300 pixels wide and 150 pixels high.
Learn more with MDN's Canvas tutorial
Syntax: var ctx = canvas.getContext(contextType, contextAttributes);
The HTMLCanvasElement.getContext()
method is used to obtain the rendering context and its drawing functions.
getContext()
takes one parameter, the type of context:
"2d"
leading to the creation of aCanvasRenderingContext2D
object representing a two-dimensional rendering context."webgl"
(or"experimental-webgl"
) which will create aWebGLRenderingContext
object representing a three-dimensional rendering context.
The CanvasRenderingContext2D
interface is used for drawing rectangles, text, images and other objects onto the canvas element. It provides the 2D rendering context for the drawing surface of a element.
CanvasRenderingContext2D
has many methods for drawing rectangles, text, paths etc, and to customize styles for these drawings. In this project, we used:
strokeStyle()
: Color or style to use for the lines around shapes.lineJoin()
: Defines the type of corners where two lines meet. Possible values:miter
(default),round
,bevel
.lineCap()
: Type of endings on the end of lines. Possible values:butt
(default),round
,square
.lineWidth()
: Width of linesbeginPath()
: Starts a new path by emptying the list of sub-paths. Call this method when you want to create a new path (e.g. a new line)moveTo()
: Moves the starting point of a new sub-path to the(x, y)
coordinates.lineTo()
: Connects the last point in the subpath to the(x, y)
coordinates with a straight line.stroke()
: Strokes the subpaths with the current stroke styleglobalCompositeOperation()
: a property ofCanvasRenderingContext2D
that sets the type of compositing operation to apply when drawing new shapes, where type is a string identifying which of the compositing or blending mode operations to use.
See MDN docs with diagram explanation of each composition type
offsetX
read-only property of theMouseEvent
interface provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.offsetY
read-only property of theMouseEvent
interface provides the offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
The destructuring assignment
syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
The left-hand side of the assignment define what values to unpack from the sourced variable.
Example:
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
hsl()
function define colors using the Hue-saturation-lightness model (HSL).
HSL
stands for hue, saturation, and lightness - and represents a cylindrical-coordinate representation of colors.
Handy HSL value reference
Console API allow developers to write information to the Dev Tools console in browsers, create JavaScript profiles, and start a debugging session.
In DevTools, there are several types of breakpoints available, which can be used to pause JS code for debugging:
Line-of-code
: Pause on an exact region of code.Conditional line-of-code
: Pause on an exact region of code, but only when some other condition is true.DOM
: Pause on the code that changes or removes a specific DOM node, or its children.XHR
: Pause when an XHR URL contains a string pattern.Event listener
: Pause on the code that runs after an event, such as whenclick
, is fired.Exception
: Pause on the line of code that is throwing a caught or uncaught exception.Function
: Pause whenever a specific function is called.
Read Get Started with Debugging JavaScript in Chrome DevTools to learn more.
The first parameter passed to any of the logging methods may contain one or more format specifiers. A format specifier consists of a %
symbol followed by a letter that indicates the formatting that applies to the value.
Example: Using string
and digit
formatters to insert values into the output
console.log("%s has %d points", "Sam", 100);
// Sam has 100 points
%s
: Formats the value as a string%i
or%d
: Formats the value as an integer%f
: Formats the value as a floating point value%o
: Formats the value as an expandable DOM element. As seen in the Elements panel%O
: Formats the value as an expandable JavaScript object%c
: Applies CSS style rules to the output string as specified by the second parameter
Prints a message like console.log()
, but also displays a yellow warning icon next to the logged message.
Prints a message similar to console.log()
, styles the message like an error, and includes a stack trace from where the method was called.
Prints a message like console.log(),
but also shows an icon (blue circle with white "i") next to the output.
Syntax: console.assert(expression, object)
Writes an error
to the console when the evaluated expression is false
.
Clears the console. However, if the Preserve log
checkbox is enabled, console.clear()
is disabled.
Prints a JavaScript representation of the specified object. If the object being logged is an HTML element
, then the properties of its DOM representation are printed
Starts a new logging group with an optional title.
All console output that occurs after console.group()
and before console.groupEnd()
is visually grouped together.
Example:
function name(obj) {
console.group('name');
console.log('first: ', obj.first);
console.log('middle: ', obj.middle);
console.log('last: ', obj.last);
console.groupEnd();
}
name({"first":"Wile","middle":"E","last":"Coyote"});
You can also use console.groupCollapsed()
to create a logging group that is initially collapsed instead of open.
Writes the number of times that count()
has been invoked at the same line and with the same label.
Example:
function login(name) {
console.count(name + ' logged in');
}
See Counting Statement Executions for more examples.
Starts a new timer. Call console.timeEnd()
to stop the timer and print the elapsed time to the Console. You can also use labels to run multiple timers at the same time.
Example for running console.time()
once:
console.time('total');
let arr = new Array(10000);
for (let i = 0; i < arr.length; i++) {
arr[i] = new Object();
}
console.timeEnd('total');
// total: 3.696044921875ms
Example for running console.time('total)
multiple times using label "total":
console.time('total');
console.time('init arr');
let arr = new Array(10000);
console.timeEnd('init arr');
for (let i = 0; i < arr.length; i++) {
arr[i] = new Object();
}
console.timeEnd('total');
// init arr: 0.0546875ms
// total: 2.5419921875ms
Wes also mentioned using performance.now()
The performance.now()
method returns a DOMHighResTimeStamp
, measured in milliseconds. The returned value represents the time elapsed since the time origin.
DOMHighResTimeStamp
type is a double
and is used to store a time value. The value could be a discrete point in time or the difference in time between two discrete points in time.
The Event
interface represents any event which takes place in the DOM; some are user-generated (such as mouse or keyboard events), while others are generated by APIs (such as events that indicate an animation has finished running, a video has been paused, and so forth).
The MouseEvent interface represents events that occur due to the user interacting with a pointing device (such as a mouse). Common events using this interface include click, dblclick, mouseup, mousedown, mouseenter etc
MouseEvent
properties covered in this project:
MouseEvent.shiftKey
: Returns true if the shift key was down when the mouse event was fired.
Other related properties:
MouseEvent.ctrlKey
: Returns true if the control key was down when the mouse event was fired.MouseEvent.clientX
: The X coordinate of the mouse pointer in local (DOM content) coordinates.MouseEvent.clientY
: The Y coordinate of the mouse pointer in local (DOM content) coordinates.
The HTMLMediaElement
interface adds to HTMLElement
the properties and methods needed to support basic media-related capabilities that are common to audio and video. The HTMLVideoElement <video>
and HTMLAudioElement <audio>
elements both inherit this interface.
Properties covered in this project:
HTMLMediaElement.paused
: Returns aBoolean
that indicates whether the media element is paused.HTMLMediaElement.currentTime
: Gives the current playback time in seconds. Setting this value sets the media to the new playback time. Covered in project 28HTMLMediaElement.playbackRate
: sets the rate at which the media is being played back. This is used to implement user controls for fast forward, slow motion, and so forth. The normal playback rate is multiplied by this value to obtain the current rate, so a value of 1.0 indicates normal speed.
Methods covered in this project:
HTMLMediaElement.play()
: Begins playback of mediaHTMLMediaElement.pause()
: Pauses media playbackHTMLMediaElement.duration
: gives the length of the media in seconds, or zero if no media data is available.
The HTML Video element <video>
embeds a media player which supports video playback into the document. You can use <video>
for audio content as well, but the <audio>
element may provide a more appropriate user experience.
Node.textContent
property represents the text content of a node and its descendants, and can be used to get
or set
the text content.
Konami code was originally a cheat code that appears in many Konami video games - a sequence of buttons on the game controller that enable a cheat or other effects. The code is also present as an Easter egg on a number of websites.
The KeyboardEvent.key read-only property returns the value of a key or keys pressed by the user. Its value is determined as follows:
See a full list of key values here
A JS library that randomly populars unicorn and rainbows on your webpage!
Debouncing enforces that a function not be called again until a certain amount of time has passed. "Execute this function only if 100 milliseconds have passed without it being called."
Throttling enforces a maximum number of times a function can be called over time. "Execute this function at most once every 100 milliseconds."
scrollY
is a read-only property of window
, which returns the number of pixels that the document is currently scrolled vertically.
The pageYOffset
property is an alias for the scrollY
property:
window.pageYOffset == window.scrollY; // always true
For cross-browser compatibility, use window.pageYOffset
instead of window.scrollY
.
Height (in pixels) of the browser window
viewport including, if rendered, the horizontal scrollbar.
HTMLElement.offsetTop
is a read-only property, which returns the distance of the current element relative to the top of the closest relatively positioned parent element.
In this project, Wes explained Passing By Value and Passing By Reference concepts in Javascript with examples. Researching further, I found this blog to be very helpful in explaining the two concepts:
Javascript Passing by Value vs. Reference Explained in Plain English by Chris D'Ascoli
There are 5 primitive
values in Javascript: undefined, null, boolean, string, numbers
Passing By Value means that you have two variables in memory that are assigned to one another. When we pass primitives
by value, we are making a copy of the value of a variable stored in memory, say at address 0x001
and assigning it to a stored at 0x002
.
Example:
let a;
let b = 8;
let a = b;
console.log(b); // will return 8
Both a
and b
mirror each other as they are just copies of one another.
Passing By Reference relates to objects
in Javascript (ALL objects including array
and functions
). Passing by reference involves having two references point to the same object
in memory.
This means you can mutate an object/array/function
by assigning it to another object or passing it as a parameter to a function.
Example:
let a = {language: "Javascript"}
let b = a
console.log(a) // => {language: "Javascript"}
console.log(b) => {language: "Javascript"}
a.language = "Ruby"
console.log(a) // => {language: "Ruby"}
console.log(b) // => {language: "Ruby"}
First, we created a variable a
and set it equal to an object {language: “Javascript”}
.
The equals =
operator recognizes that the value is an object, creates a new spot in memory, and points a
to it.
We then create a new variable b
and set it equal to a
. The equals =
operator identifies we are dealing with objects and points b
to the same location in memory that a
is pointed to.
No new location or object in memory was created, rather both variables a
& b
are pointing to the SAME location (address).
So, when we mutate the value of variable a
, changing “Javascript” to “Ruby”), the results of a
& b
are the same since b
points to the same location as a
.
Therefore, in order to make a copy of an object/array/function
, we have to use different methods:
- ES6 spread syntax
...
, method covered in Project 6 Array.slice()
method covered in Project 7Array.prototype.concat()
: Theconcat()
method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.Array.from()
: creates a new Array instance from an array-like or iterable object.
Object.assign()
: The Object.assign()
method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
In another word, Object.assign
performs a shallow copy and merges the original object's enumerable keys, but any nested properties remain shared between the original and the clone.
Syntax: Object.assign(target, ...sources)
However, there are some side effects with this method:
- all accessors or properties with a
getter
and asetter
, will be copied as data, invoking thegetter
during the copy - all
Symbol
keys, that are enumerable by default, will be copied too, making Symbols less private or protected than we think
How To Copy Objects in JavaScript is a helpful blog by Andrea Giammarchi, he also provided his patched version of Object.assign
function that preserves accessors.
Understanding Object Cloning in Javascriptis another useful blog for reference by Cédric Ronvel.
When it is ok to share some data across variables, we can use shallow copy.
However, when we need to clone a deep and complex data structure (e.g. a tree
), we will have to perform a deep copy
. A deep copy
will recursively clone every objects it encounters. The clone and the original object will not share anything, so the clone will be a fully distinct entity.
There's also an issue with circular reference
- when an object refers to itself in a property.
Example:
function Foo() {
this.abc = "Hello";
this.circular = this;
}
var foo = new Foo();
alert(foo.circular.circular.circular.circular.circular.abc);
// foo contains a reference to itself.
Currently, there is no native mechanism to perform a deep copy in JavaScript. So, it is best to use external libraries such as cloner or tree-kit, frameworks often have their own libraries such as React's immutability-helper
Wes showed us that we can create a deep clone
with:
JSON.parse(JSON.stringify(obj))
JSON.stringify()
: converts a JavaScript value to a JSON stringJSON.parse()
: parses a JSON string, then returns the JavaScript value or object described by the JSON string.
This is also the suggested solution on MDN guides, and a great StackOverflow thread discussed performance for various methods.
The Storage
interface of the Web Storage API provides access to the sessionStorage
or localStorage
for a particular domain, allowing you to for example add, modify or delete stored data items.
LocalStorage
allows you to access a Storage
object for the current origin, the stored data is saved across browser sessions with no expiration set.
Syntax: storage.getItem(keyName);
When passed a key name, the getItem()
method will return that key's value.
Syntax: storage.setItem(keyName, keyValue);
when passed a key name and value, the setItem()
method will add that key to the storage, or update that key's value if it already exists.
When invoked, the clear()
method will empty all keys out of the storage.
The Event
interface's preventDefault()
method tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be. The event continues to propagate as usual, unless one of its event listeners calls stopPropagation()
or stopImmediatePropagation()
, either of which terminates propagation at once.
In this project, we used this method to stop the page from refreshing upon clicking the form submit
button.
Event delegation
allows us to avoid adding event listeners to specific nodes (e.g. <li>
items once created).
Instead, the event listener is added to one parent (e.g. ul
). That event listener analyzes bubbled events to find a match on child elements.
For block-level elements, offsetTop
, offsetLeft
, offsetWidth
, and offsetHeight
describe the border box of an element relative to the offsetParent
.
The HTMLElement.offsetLeft
read-only property returns the number of pixels that the upper left corner of the current element is offset to the left within the HTMLElement.offsetParent
node.
The HTMLElement.offsetTop
read-only property returns the distance of the current element relative to the top of the offsetParent
node.
The Math.round()
function returns the value of a number rounded to the nearest integer. However, there may be rounding errors due to floating-number
arithmetic.
Example: Rounding 1.005 should have an expected result of 1.01, but it returns 1.
CSS Syntax: text-shadow: h-shadow v-shadow blur-radius color|none|initial|inherit;
JS Syntax: object.style.textShadow="2px 5px 5px red"
The text-shadow property adds shadow to text, default value is none.
Regular expressions are patterns used to match character combinations in strings.
The parseFloat()
function parses an argument and returns a floating point number. We used this function to convert time from string
to number
.
The Math.floor()
function returns the largest integer less than or equal to a given number.
The Navigator
interface represents the state and the identity of the user agent. It allows scripts to query it and to register themselves to carry on some activities.
A Navigator
object can be retrieved using the read-only window.navigator
property. The interface gives us access to several properties and methods.
The Navigator.mediaDevices
read-only property returns a MediaDevices
object, which provides access to connected media input devices like cameras and microphones, as well as screen sharing.
The MediaDevices
interface provides access to connected media input devices like cameras and microphones, as well as screen sharing. In essence, it lets you obtain access to any hardware source of media data.
The MediaDevices getUserMedia()
method prompts the user for permission to use a media input which produces a MediaStream
with tracks containing the requested types of media, e.g. video, audio etc.
It returns a Promise
that resolves to a MediaStream
object. If the user denies permission, or matching media is not available, then the promise is rejected with PermissionDeniedError
or NotFoundError
respectively.
The URL interface represents an object providing static methods used for creating object URLs.
The Window.URL
property returns an object that provides static methods used for creating and managing object URLs. It can also be called as a constructor to construct URL
objects.
The URL.createObjectURL()
static method creates a DOMString
containing a URL
representing the object given in the parameter. The URL lifetime is tied to the document in the window
on which it was created.
The setInterval()
method repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. It returns an interval
ID which uniquely identifies the interval, so you can remove it later by calling clearInterval()
. This method is offered on the Window
and Worker
interfaces.
We learned about HTML Canvas in Project 8. The CanvasRenderingContext2D
interface is used for drawing objects onto the canvas
element.
In this project, we used the following methods:
Syntax: ctx.drawImage(image, dx, dy, dWidth, dHeight)
CanvasRenderingContext2D.drawImage()
method takes in several arguments:.
image
: An element to draw into the canvas contextdx
: The X coordinate in the destination canvas at which to place the top-left corner of the sourceimage
dy
: The Y coordinate in the destination canvas at which to place the top-left corner of the sourceimage
dWidth
: The width to draw the image, this allows scaling of the drawn image. If not specified, the image is not scaled in width when drawn.dHeight
: The height to draw the image. this allows scaling of the drawn image. If not specified, the image is not scaled in height when drawn.
Syntax: ctx.getImageData(sx, sy, sw, sh)
The CanvasRenderingContext2D.getImageData()
method returns an ImageData
object representing the underlying pixel data for the area of the canvas denoted by the rectangle which starts at (sx, sy)
and has an sw
width and sh
height.`
The CanvasRenderingContext2D.globalAlpha
property specifies the alpha value that is applied to shapes and images before they are drawn onto the canvas. The value is in the range from 0.0 (fully transparent) to 1.0 (fully opaque).
The ImageData
interface represents the underlying pixel data of an area of a <canvas>
element, it has 3 properties:
ImageData.data
: Is aUint8ClampedArray
representing a one-dimensional array containing the data in the RGBA order, with integer values between 0 and 255.
Example: [255, 0, 0, 1] // red, green, blue, alpha
Note The Uint8ClampedArray
typed array represents an array of 8-bit unsigned integers clamped to 0-255. It has limited properties and methods, such as .length()
. However, it does not have higher order functions like map
or reduce
for iterating over the array elements.
ImageData.height
: the actual height, in pixels, of theImageData
.ImageData.width
: the actual width, in pixels, of theImageData
.
The Web Speech API
enables you to incorporate voice data into web apps. The Web Speech API has two parts: SpeechSynthesis
(Text-to-Speech), and SpeechRecognition
(Asynchronous Speech Recognition.)
-
Speech recogition is accessed via the
SpeechRecognition
interface, which provides the ability to recognize voice context from an audio input (normally via the device's default speech recognition service) and respond appropriately. -
Speech synthesis is accessed via the
SpeechSynthesis
interface, a text-to-speech component that allows programs to read out their text content (normally via the device's default speech synthesiser.)
Different voice types are represented by SpeechSynthesisVoice
objects, and different parts of text that you want to be spoken are represented by SpeechSynthesisUtterance
objects. You can get these spoken by passing them to the SpeechSynthesis.speak()
method.
MDN Guide to Using the Web Speech API
The SpeechRecognition
interface of the Web Speech API is the controller interface for the recognition service; this also handles the SpeechRecognitionEvent
sent from the recognition service.
Generally you'll use the interface's constructor to create a new SpeechRecognition
object:SpeechRecognition.SpeechRecognition()
This object has a number of event handlers available for detecting when speech is input through the device's microphone.
SpeechRecognition
methods used in this project:
-SpeechRecognition.start()
The start()
method of the Web Speech API
starts the speech recognition service listening to incoming audio with intent to recognize grammars associated with the current SpeechRecognition.
- SpeechRecognition.interimResults
The
interimResults
property controls whether interim results should be returned (true
) or not (false
.) Interim results are results that are not yet final (i.e.SpeechRecognitionResult.isFinal = false
)
The default value for interimResults
is false
.
The Navigator
interface represents the state and the identity of the user agent. It allows scripts to query it and to register themselves to carry on some activities.
In this project, our user agent is a user's mobile phone.
Navigator.geolocation
returns a Geolocation
object allowing accessing the location of the device.
The Geolocation
interface represents an object able to programmatically obtain the position of the device. It gives Web content access to the location of the device. This allows a website or app to offer customized results based on the user's location.
Geolocation
methods used in this project:
watchPosition()
method is used to register a handler function that will be called automatically each time the position of the device changes. For example, console.log(data)
. You can also specify an error handling callback function.
If successful, this method will return a Position
object with data on the device's position.
The Position interface
represents the position of the user device at a given time. The position
is represented by a Coordinates
object which contains various data.
The Coordinates interface
represents the longitude, latitude, altitude of the device on Earth, current speed, as well as the accuracy with which these properties are calculated.
Some of Coordinates properties:
Coordinates.latitude
Coordinates.longitude
Coordinates.altitude
Coordinates.accuracy
Coordinates.speed
Coordinates.heading
Returns a double representing the direction in which the device is traveling. This value, specified in degrees, indicates how far off from heading true north the device is and the direction is determined clockwise.- True north = 0 degrees
- East = 90 degrees
- South = 180 degrees
- West = 270 degrees
If speed is 0, heading is NaN
. If the device is unable to provide heading information, this value is null
.
The Element.getBoundingClientRect()
method returns the size of an element and its position relative to the viewport.
The returned value is a DOMRect
object which is the union of the rectangles returned by getClientRects()
for the element, i.e., the CSS border-boxes associated with the element.
The result is the smallest rectangle which contains the entire element, with read-only left, top, right, bottom, x, y, width
, and height
properties describing the overall border-box in pixels.
Properties other than width
and height
are relative to the top-left of the viewport.
In project 20, we learned to use the SpeechRecognition
interface. In this project, we learned about the SpeechSynthesisUtterance
interface to add speech service to our web app.
The SpeechSynthesisUtterance
interface of the Web Speech API represents a speech request. It contains the content the speech service should read and information about how to read it (e.g. language, pitch and volume.)
The constructor returns a new SpeechSynthesisUtterance
object instance, which has various properties that can get and set the language, pitch, speed, voice, volume of the utterance (or speech) to be spoken.
In this project, we defined these properties:
SpeechSynthesisUtterance.text
: Gets and sets the text that will be synthesised when the utterance is spoken.SpeechSynthesisUtterance.pitch
: Gets and sets the pitch at which the utterance will be spoken at.SpeechSynthesisUtterance.rate
: Gets and sets the rate at which the utterance will be spoken at.SpeechSynthesisUtterance.voice
: Gets and sets the voice that will be used to speak the utterance.
The SpeechSynthesis
interface of the Web Speech API is the controller interface for the speech service; this can be used to retrieve information about the synthesis voices available on the device, start and pause speech etc.
In this project, we used these methods:
SpeechSynthesis.getVoices()
: Returns a list ofSpeechSynthesisVoice
objects representing all the available voices on the current device. (e.g. English, French)
This list is retrieved with an event handler, SpeechSynthesis.onvoiceschanged
which fires when the list of SpeechSynthesisVoice
objects that would be returned by the SpeechSynthesis.getVoices() method has changed.
SpeechSynthesis.speak()
: Adds anutterance
to the utterance queue; it will be spoken when any other utterances queued before it have been spoken.SpeechSynthesis.cancel()
: Removes all utterances from the utterance queue.
The bind()
method creates a new function that, when called, has its this
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
Syntax: function.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
: The value to be passed as the this
parameter to the target function when the bound function is called.
arg1, arg2, ...
:
These arguments (if any) follow the provided this
value, and are then inserted at the start of the arguments passed to the target function, followed by the arguments passed to the bound function, whenever the bound function is called.
MDN Guides provide two great examples showing how to create a bound function
and a partially applied function
.
In this project, we used the .bind()
method in the stopButton
event handler to create a partially applied function:
function toggle(startOver = true){
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
};
}
stopButton.addEventListener('click', toggle.bind(null, false));
When the stopButton
is clicked, the event handler will execute the toggle()
function with an initial value false
i.e. toggle(false)
which will call speechSynthesis.cancel()
method to stop speech utterance.
They key takeaway for this project was the need to account for nav bar height (offsetHeight
) when we fix it to the top of page, and CSS tweaks.
When user scroll past nav bar, we:
- added a
class
to<body>
element - account for nav bar's height by adding padding to
<body>
:
document.body.style.paddingTop = nav.offsetHeight;
- updated CSS for
.fixed-nav nav
: changeposition
fromrelative
tofixed
Note that CSS transitions do not work on auto dimensions! Here's a great article by Brandon Smith, Using CSS Transitions on Auto Dimensions
- updated CSS for
.fixed-nav li.logo
: changemax-width: 500px
Here, Wes scaled up the content (wrapped in <div>
with class name site-wrap
):
- updated CSS for
.fixed-nav .site-wrap
: changetransform
scale from 0.98 to 1
The scale()
CSS function defines a transformation that resizes an element on the 2D plane. The amount of scaling is defined by a vector, so it can resize the horizontal and vertical dimensions at different scales.
Syntax:
scale(sx)
// sx: A number representing the abscissa of the scaling vector.
scale(sx, sy)
// sy: A number representing the ordinate of the scaling vector. If not defined, its default value is sx, resulting in a uniform scaling that preserves the element's aspect ratio.
They key takeaway for this project is DOM Event Architecture.
Here's a great diagram, showing an event dispatched in a DOM tree using the DOM event flow:
Event objects are dispatched to an event target. But before dispatch can begin, the event object’s propagation path must first be determined.
Propagation is also known as an event target chain - the ordered set of current event targets though which an event object will pass sequentially on the way to and back from the event target.As the event propagates, each current event target in the propagation path is in turn set as the currentTarget.
Once the propagation path has been determined, the event object passes through one or more event phases:
Capture Phase
: The event object propagates through the target’s ancestors from theWindow
to the target’s parentTarget phase
: The event object arrives at the event object’s event target. This phase is also known as the at-target phase. If the event type indicates that the event doesn’t bubble, then the event object will halt after completion of this phase.Bubble phase
: The event object propagates through the target’s ancestors in reverse order, starting with the target’s parent and ending with theWindow
.
A phase will be skipped if it is not supported, all phases will be skipped if stopPropagation()
has been called prior to the dispatch.
Syntax:
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
The EventTarget
method addEventListener()
sets up a function that will be called whenever the specified event is delivered to the target.
type
: A case-sensitive string representing the event type to listen for (e.g. click, mousedown)listener
: a callback functionoptions
(optional attributes):capture
: aBoolean
, iffalse
thecapture phase
will be skippedonce
: aBoolean
indicating that thelistener
should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.passive
aBoolean
, iftrue
thelistener
will never callpreventDefault()
.
Syntax:
style.setProperty(propertyName, value, priority);
priority
: is a DOMString allowing the important
CSS priority to be set. If not specified, treated as the empty string.
setProperty()
method sets a new value for a property on a CSS style declaration object.
Example:
background.style.setProperty('height', `${coords.height}px`);
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('transform', `translate(${coords.left}px,${coords.top}px)`);
In this project, Wes used .pageX
to get the horizontal X coordinate of the mousemove
event relative to the whole document.
UIEvent.pageY
gets the vertical Y coordinate of the mousemove
event relative to the whole document.
NOTE MDN notes that this feature is only valid on MouseEvent interface!
Element.scrollLeft
property gets or sets the number of pixels that an element's content is scrolled to the left.
The CSS direction property sets the direction of text, table columns, and horizontal overflow.
rtl
: for languages written from right to left (e.g. Chinese, Hebrew, Arabic)ltr
: for languages written from left to right (e.g. English)
Note that if the element's direction of the element is rtl
(right-to-left) then scrollLeft = 0
when the scrollbar is at its rightmost position (at start of the scrolled content) and then increasingly negative as you scroll towards the end of the content.
Added UIEvent.pageY
and HTMLMediaElement.playbackRate
to corresponding sections above.
The toFixed()
method formats a number using fixed-point notation, where a number that has a fixed number of digits after (and sometimes also before) the decimal point.
Syntax:
new Date();
new Date(value);
new Date(dateString);
new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);
Date
objects are based on a time value that is the number of milliseconds since 1 January 1970 UTC.
Date.now()
method: Returns the numeric value corresponding to the current time - the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC, with leap seconds ignored.
The document.title
property gets or sets the current title of the document.
isTrusted
is a read-only property of the Event
interface, it is a boolean
value.
- if
isTrusted = true
, the event was generated by a user action (e.g. mouse click) - if
isTrusted = false
, the event was created or modified by a script or dispatched viadispatchEvent
, for example in automated testing.