- 1. Basilica!
- 1.2. Homework
- 1.3. Reading
- 1.4. Goals
- 1.5. Initialize GIT with .gitignore
- 1.6. NPM Initialization and Installs
- 1.7. Basilica Project
- 1.8. Responsive Images
- 1.9. Flex Layout
- 1.10. The Branding Header
- 1.11. Navigation
- 1.12. CSS Grid
- 1.13. Sass
- 1.14. JavaScript
- 1.15. JavaScript Popover
- 1.16. DOM Scripting Methods Used
- 1.17. A Dynamic Popover
- 1.18. Notes
- 1.19. Expressions
- 1.20. Statements
Create separate popovers for each button in the navigations. The popovers should each have different messages.
- See how far you can get in Grid Garden
- MDN on CSS Grid
- The Absolute Beginners Guide to NPM
- What is GIT?
- Review GIT and Github set up and branching
- Review NPM set up and installing
- Continue working with Flexbox and introduce CSS Grids
- Understand and set up a CSS toolchain using SASS
- Review basic DOM manipulation techiques
- Understand how to create an element and insert it into the DOM
$ git init
$ git add .
$ git commit -m 'initial commit'
Create a branch:
$ git branch inclass
$ git checkout inclass
$ touch .gitignore // edit to include 'node_modules'
Create a branch in VS Code and merge it back into another.
Log into Github and create a new repo.
Follow the instructions to create a remote and push the master branch and inclass branch to the remote.
- Create a manifest (package.json)
- Install a package
- set up a gitignore
$ npm init
$ npm install browser-sync sass concurrently --save-dev
Note:
- sass, concurrently
- package.json
- package-lock.json
- dependencies
- node_modules folder
- why the need for
.gitignore
?
Browser Sync CLI documentation
"scripts": {
"start": "browser-sync start --directory --server 'app' --files 'app'",
},
Remember, if the repo comes with a package.json file (aka 'manifest') run npm install
to install.
In the terminal:
$ npm run start
Open app/index.html
in VS Code and examine the HTML with regards to the recipe schema at schema.org.
Here is an article that addresses the recipe schemas but note that there are many different kinds.
Have a look at a sample recipe and note the schema in the inspector.
Note the <abbr>
tag and the absence of a wrapper div (even though the design shows a centered document).
Examine the starter CSS. Note the use of max-width
on the body selector - we applied these to a div in the past.
Note li > h4
selector. It is used to select elements with a specific parent. In this case it will select h4
tags only when they are proceeded by an li
.
Here's a complete listing of selector types in CSS.
Note the css variables:
html {
--basil-green: #88a308;
--dark-gray: #333333;
--light-gray: #e4e1d1;
--light-green: #f5faef;
--orange: #f90;
--light-orange: #ebbd4e;
--red: #f00;
--max-width: 840px;
--breakpoint: 640px;
}
CSS variables are applied as follows:
<property>: var(--<variable-name>);
For example:
color: var(--basil-green);
Note also: the transition property on the anchor selector. This is a shortcut for:
transition-property: color;
transition-duration: 1s;
transition-timing-function: linear;
or transition: color 0.2s linear;
Confine this effect to anchors within the content div.
Replace the generic hover with:
.content a:hover {
color: var(--basil-green);
}
Responsive Images are a critical component of responsive design:
img {
width: 100%;
height: auto;
}
You should always use width: 100%
on images (and videos).
Replace the lone img tag in the HTML with figure
and figcaption
tags:
<figure>
<img src="img/pesto.jpg" alt="Italian pesto" />
<figcaption>
Classic, simple basil pesto recipe with fresh basil leaves, pine nuts,
garlic, Romano or Parmesan cheese, extra virgin olive oil, and salt and
pepper.
</figcaption>
</figure>
A <figure>
is an image, illustration, or diagram that is referenced in the main flow of a document, but that can be moved to another part of the document without affecting the main flow.
We want to display identical image content, just larger or smaller depending on the device. The standard <img>
element only lets you point the browser to a single source file. We will use two new attributes — srcset
and sizes
— to provide additional source images along with hints to help the browser pick the right one.
- Upload
pesto.jpg
to a generator such as responsivebreakpoints.com. Download the zip file and place the unzipped folder in theimg
directory.
Replace the img
tag in index.html with a picture tag:
<picture>
<img
sizes="
(max-width: 320px) 380px,
(max-width: 768px) 740px,
(max-width: 1024px) 980px,
(max-width: 1280px) 100vw, 1280px"
srcset="
img/pesto/pesto_iodywc_c_scale,w_380.jpg 380w,
img/pesto/pesto_iodywc_c_scale,w_780.jpg 780w,
img/pesto/pesto_iodywc_c_scale,w_1069.jpg 1069w,
img/pesto/pesto_iodywc_c_scale,w_1337.jpg 1337w,
img/pesto/pesto_iodywc_c_scale,w_1380.jpg 1380w
"
src="img/pesto/pesto_iodywc_c_scale,w_1380.jpg"
alt="Italian pesto"
/>
</picture>
srcset
defines the set of images we will allow the browser to choose between, and what size each image is.
It consists of:
- Am image path / filename
- A space
- The image's inherent width (real size) in pixels using a
w
unit (notpx
).
sizes
defines a set of media conditions (e.g. screen widths) and indicates what image size would be best to choose, when certain media conditions are true. In this case, before each comma we write:
- A media condition e.g.
(max-width:480px)
- here "when the viewport width is 480 pixels or less" - A space
- The width of the slot the image will fill when the media condition is true (440px.). You can get this from the inspector by inspecting the image at a variety of screen widths.
The browser ignores everything after the first matching condition, so be careful how you order the media conditions.
With these attributes in place, the browser will:
- Look at its device width.
- Work out which media condition in the sizes list is the first one to be true.
- Look at the slot size given to that media query.
- Load the image referenced in the
srcset
list that most closely matches the chosen slot size.
Using this technique can save a lot of bandwidth. Older browsers that don't support these features will just ignore them, and go ahead and load the image referenced in the src
attribute.
You can check the results of your work by viewing the Network tab in the inspector and noting which image was downloaded at a variety of screen sizes.
The <picture>
tag can be used for cropping or modifying images for different media conditions or offering different image formats when certain formats are not supported by all browsers. See the example on MDN.
You need a server to output multiple mage sizes and formats. There are specialized services such as Cloudinary available. At a bare minimum, should also run your images through a processor such as imageOptim.
<img
src="https://res.cloudinary.com/deedee/image/upload/w_200,h_200,c_crop/v1623521871/samples/pesto.jpg"
alt="basil"
/>
<img
src="https://res.cloudinary.com/deedee/image/upload/w_200,e_grayscale/v1623521871/samples/pesto.jpg"
alt="basil"
/>
The two column view applies only to widescreen.
We will make the article and aside run side by side by applying flex to their parent container within a mobile first breakpoint:
@media (min-width: 640px) {
.content {
display: flex;
}
}
Note: we cannot use a CSS variable as a breakpoint:
@media (min-width: var(--breakpoint)) {
}
A media query is not an element selector so it does not inherit styles.
We can use the flex property on the flex children to manipulate the columns:
@media (min-width: 640px) {
.content {
display: flex;
}
article {
flex: 1 0 60%;
}
}
The flex property is used on flex children only. We are using a shortcut here which includes flex-grow, flex-shrink, and flex-basis
. Default is Default is 0 1 auto
.
Here is the long form:
article {
flex-grow: 1;
flex-shrink: 0;
flex-basis: 60%;
}
Use a background color and box-shadow to color the aside:
@media (min-width: 640px) {
.content {
display: flex;
}
article {
flex: 1 0 60%;
}
aside {
background: var(--light-green);
box-shadow: -4px 0px 4px #ddd;
}
}
Add some padding to the two columns outside the media query so it applies to both large and small screens:
article,
aside {
padding: 1rem;
}
Format the footer;
footer {
background-color: var(--basil-green);
padding: 1rem;
border-radius: 0 0 4px 4px;
margin-bottom: 2rem;
}
Pseudo-elements: Pseudo-classes:
::first-letter :hover
::first-line :visited
::before :link
::after :active
::selection :target
:focus
Some ideas for using pseudo-elements.
e.g.: Selected text:
::selection {
background: var(--basil-green);
color: #fff;
}
Add the green background to the branding div.
header {
height: 120px;
background: var(--basil-green);
border-radius: 8px 8px 0px 0px;
}
Note: this is one of the rare occasions that we will use the height property. We use it here because the header does not contain dynamic content.
Add the custom font (top of the css file):
@import url(futura/stylesheet.css);
Note - To convert fonts to web formats see Font Squirrel. This requires an additional call to the server to fetch the additional css when the browser renders the file.
header h1 {
background: url(img/basil.png) no-repeat;
font-family: FuturaStdLight, sans-serif;
font-weight: normal;
color: #fff;
font-size: 5rem;
}
Note: when using custom fonts like this font-weight: normal;
is necessary because by default header tags like h1 are bold and we do not have a bold version of the font here.
The background image is 272px by 170px.
Since background images fill the container we can manipulate it using padding:
header h1 {
padding-left: 260px;
padding-top: 90px;
...;
}
We cannot see the text because we have added padding. Use transform to tweak the positioning:
header h1 {
transform: translateX(-100px);
transform: translateY(-80px);
...;
}
Note the transform in the inspector - there is an error.
We have to use this format:
header h1 {
transform: translate(-100px, -80px);
...;
}
Note: transforms are an important property, especially when it comes to creating animations. We'll create an example now.
Note the beta link in the header:
<header>
<h1>Basilica!</h1>
<a class="beta" href="#">Beta</a>
</header>
Absolutely position the beta element (we can do this in the context of the header because we apply position: relative
to it earlier).
header a.beta {
background: url('img/burst.svg') no-repeat;
color: #fff;
font-size: 1.5rem;
position: absolute;
top: -20px;
right: 10px;
width: 85px;
height: 85px;
line-height: 85px;
text-align: center;
text-transform: uppercase;
}
Note: the use of img/burst.svg
for the background image. Examine the it in the editor.
Note: the use of line-height to set the leading to the same height as the containing element. This allows the text to vertically center.
Note the use of position absolute. We will give this element a positioning context by applying position absolute to its containing element:
header {
position: relative;
...;
}
Add a hover, transform and animate:
header a.beta {
transform: rotate(20deg);
transition: all 1s ease;
...;
}
header a.beta:hover {
transform: rotate(0deg) scale(1.2);
}
Examine the site for problems in a narrow browser.
We will attempt a mobile first design strategy. Edit the css to display for small screen first:
header h1 {
background: url(img/basil.png) no-repeat;
font-family: FuturaStdLight, sans-serif;
font-weight: normal;
color: #fff;
font-size: 5rem;
}
And add features for the large screen within a media query:
@media (min-width: 640px) {
header h1 {
padding-left: 240px;
padding-top: 90px;
transform: translate(-100px, -80px);
background-position: top left;
}
...;
}
Additional tweaks for the small screen might include:
- Removing the body margin top (and adding it back for the wide screen):
body {
font: 100%/1.5 'Segoe UI', Candara, 'Bitstream Vera Sans', 'DejaVu Sans', 'Bitstream Vera Sans',
'Trebuchet MS', Verdana, 'Verdana Ref', sans-serif;
color: var(--dark-gray);
max-width: var(--max-width);
/* margin: 0 auto;
margin-top: 24px; */
}
@media (min-width: 640px) {
body {
margin: 0 auto;
margin-top: 24px;
}
...;
}
- Removing the rounded corners on small screen (and adding it back on wide screens):
header {
position: relative;
height: 120px;
background: var(--basil-green);
/* border-radius: 8px 8px 0px 0px; */
}
@media (min-width: 640px) {
header {
border-radius: 8px 8px 0px 0px;
}
...;
}
Remember: there is no hover in touch screen devices.
Add the code below one rule at a time:
nav {
background: var(--light-gray);
border-top: 0.5rem solid var(--light-orange);
padding: 0.5rem;
display: flex;
align-items: center;
}
nav ul {
display: flex;
}
nav li {
list-style: none;
margin-right: 0.5rem;
}
nav p {
margin-right: auto;
}
Note the margin-right property on the paragraph and the effect it has on the positioning on the navigation links.
Remove it and add justify-content
to the flex parent:
nav {
justify-content: space-between;
flex-wrap: wrap;
...;
}
/* nav p {
margin-right: auto;
} */
Note: the flex-wrap property allows the paragraph to stack on small screens.
nav a {
text-align: center;
font-size: 1.5rem;
padding: 8px;
color: #fff;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
border-radius: 6px;
}
The gradients for the buttons:
.nav-storeit a {
background: linear-gradient(to bottom, #fcde41 1%, #dfa910 100%);
}
.nav-storeit a:hover {
background: linear-gradient(to bottom, #dfa910 0%, #fcde41 100%);
}
.nav-pickit a {
background: linear-gradient(to bottom, #abc841 0%, #6b861e 100%);
}
.nav-pickit a:hover {
background: linear-gradient(to bottom, #6b861e 1%, #abc841 100%);
}
.nav-cookit a {
background: linear-gradient(to bottom, #6f89c7 0%, #344e8b 100%);
}
.nav-cookit a:hover {
background: linear-gradient(to bottom, #344e8b 1%, #6f89c7 100%);
}
Make all the buttons the same width. Try with and without the inline-block
.
nav a {
min-width: 120px;
display: inline-block;
...;
}
Note: this is a setting which will likely need to be changed to accomodate small screens.
CSS Tricks offers a guide to CSS grid.
Flexbox operates in a single dimension: x or y. CSS Grid operates on both the x and y axis.
Our current use of Flexbox to style the content columns operates in a single (horizontal or x) dimension so flex is a viable option.
Nevertheless, we will use CSS Grid for the primary layout in order to introduce some of its features in this simple use case.
Remove the flex statements and use a grid display, define columns, and set the start and end points for the grid children:
@media (min-width: 640px) {
.content {
/* display: flex; */
display: grid;
grid-template-columns: 20% 20% 20% 20% 20%;
}
article {
grid-column-start: 1;
grid-column-end: span 3;
/* flex: 1 0 60%; */
}
aside {
grid-column-start: 4;
grid-column-end: span 2;
background: var(--light-green);
box-shadow: -4px 0px 4px #ddd;
}
...;
}
Finally, by moving using display grid to the body selector, we can use grid areas to define our layout:
@media (min-width: 600px) {
body {
margin: 0 auto;
margin-top: 1.5rem;
display: grid;
grid-template-areas:
'header'
'nav'
'content'
'footer';
}
header {
border-radius: 8px 8px 0px 0px;
grid-area: header;
}
header h1 {
padding-left: 240px;
padding-top: 90px;
transform: translate(-100px, -80px);
background-position: top left;
}
nav {
grid-area: nav;
}
.content {
grid-area: content;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-column-gap: 1rem;
}
article {
grid-column: span 3;
}
aside {
grid-column: span 2;
background: var(--light-green);
box-shadow: -4px 0px 4px #ddd;
}
footer {
grid-area: footer;
}
}
Demo:
header {
grid-area: footer;
}
There is a complete CSS file available at this gist
Examine this branch on the repo.
Note the scss
folder and its contents.
Earlier we used NPM to install Sass:
npm install sass --save-dev
Stop the server and add a script to package.json for processing:
"scripts": {
...
"sass": "sass scss/styles.scss app/css/styles.css --watch --source-map"
},
Dart Sass CLI documentation
To run both scripts at the same time edit the scripts in package.json:
"scripts": {
"server": "browser-sync start --directory --server 'app' --files 'app'",
"sass": "sass scss/styles.scss app/css/styles.css --watch --source-map",
"start": "npm run server & npm run sass"
},
- Create a
scss
folder at the top level of our repo - Copy
styles.css
into it - Rename
scss/styles.css
toscss/styles.scss
.
Run npm start
(note: the word 'run' is optional when using start).
Test it by re-adding the following to the top of styles.scss
:
* {
color: red !important;
}
Note that sass is less tolerant of errors than regular css. Try
* { color red !important };
.
Sass has its own variables system, e.g.:
$basil-green: #88a308;
$breakpoint-med: 640px;
Here is an example showing sass variables for breakpoints and more:
Example:
$break-five: 81.25em;
// 1300px
$break-four: 71.25em;
// 1140
$break-three: 61.25em;
// 980
$break-two: 46.25em;
// 760
$break-one: 22.5em;
// 360
$radius: 0.25rem;
$fonts: 'Source Sans Pro', Helvetica, Clean, sans-serif;
$red: #f00;
Usage example:
$red: #f00;
* {
color: $red !important;
}
Add the sample SASS variables to a new file in a new folder scss/imports/_variables.scss
and include it at the top of styles.scss
:
@import 'imports/variables';
Note the syntax differences between a native CSS import as well as the underscore in the file name. We will come back to that later.
Test using the $red
variable. Try changing it. Remove it.
Sass will let you nest your CSS selectors in a way that mirrors the hierarchy of your HTML. Be aware that overly nested rules will result in over-qualified CSS that could prove hard to maintain and is generally considered bad practice.
Nest the header related styles in styles.scss
:
header {
position: relative;
height: 120px;
background: var(--basil-green);
h1 {
background: url(img/basil.png) no-repeat;
font-family: FuturaStdLight, sans-serif;
font-weight: normal;
color: #fff;
font-size: 5rem;
}
a.beta {
background: url('img/burst.svg') no-repeat;
color: #fff;
font-size: 1.5rem;
position: absolute;
top: -20px;
right: 10px;
width: 85px;
height: 85px;
line-height: 85px;
text-align: center;
text-transform: uppercase;
transform: rotate(20deg);
transition: all 1s ease;
}
a.beta:hover {
transform: rotate(0deg) scale(1.2);
}
}
Sass partials allow you to create separate function or feature specific style sheets using imports and helps maintain a large code base.
Examples Bootstrap and its SASS roots
Create imports/_header.scss
and cut and paste the nested header material from _styles.scss
into it. Import it to the main stylesheet with:
@import 'imports/header';
Note the underscore in the file name. If you add an underscore to the start of the file name, Sass won’t compile it. So, if you don’t want header.scss
to compile to header.css
, name the file with an undercore _header.scss
instead. Files used this way are called partials in Sass.
Note: SASS allows you to use JavaScript style comments - //
. These comments do not get compiled into the css file. Traditional CSS comments ( /* ... */
) do.
Create a nested block for the nav in styles.scss:
nav {
background: var(--light-gray);
border-top: 0.5rem solid var(--light-orange);
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
ul {
display: flex;
}
li {
list-style: none;
margin-right: 0.5rem;
}
a {
text-align: center;
font-size: 1.5rem;
padding: 8px;
color: #fff;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
border-radius: 6px;
min-width: 120px;
display: inline-block;
}
.nav-storeit a {
background: linear-gradient(to bottom, #fcde41 1%, #dfa910 100%);
}
.nav-storeit a:hover {
background: linear-gradient(to bottom, #dfa910 0%, #fcde41 100%);
}
.nav-pickit a {
background: linear-gradient(to bottom, #abc841 0%, #6b861e 100%);
}
.nav-pickit a:hover {
background: linear-gradient(to bottom, #6b861e 1%, #abc841 100%);
}
.nav-cookit a {
background: linear-gradient(to bottom, #6f89c7 0%, #344e8b 100%);
}
.nav-cookit a:hover {
background: linear-gradient(to bottom, #344e8b 1%, #6f89c7 100%);
}
}
Create a partial _nav.scss
in the imports folder and cut and paste the nested nav block into it. Import the partial back into styles.scss
with:
@import 'imports/nav';
Examine the CSS in the browser's inspector. Note the mapping.
One of the best things about nesting in SASS is how it allows you to organize media queries and keep them aligned with the selector.
Cut the body rule from the breakpoint and add the styling to the initial body rule as shown:
body {
font: 100%/1.5 'Segoe UI', Candara, 'Bitstream Vera Sans', 'DejaVu Sans', 'Bitstream Vera Sans',
'Trebuchet MS', Verdana, 'Verdana Ref', sans-serif;
color: var(--dark-gray);
max-width: var(--max-width);
@media (min-width: $break-two) {
margin: 0 auto;
margin-top: 1.5rem;
display: grid;
grid-template-areas:
'header'
'nav'
'content'
'footer';
}
}
Note: since SASS is a transpiler we can use SASS variables - $break-two
- for break points.
Perform a similar action for the header partial use nesting for the media queries:
header {
position: relative;
height: 120px;
background: var(--basil-green);
@media (min-width: $break-two) {
border-radius: 8px 8px 0px 0px;
grid-area: header;
}
h1 {
background: url(img/basil.png) no-repeat;
font-family: FuturaStdLight, sans-serif;
font-weight: normal;
color: #fff;
font-size: 5rem;
@media (min-width: $break-two) {
padding-left: 240px;
padding-top: 90px;
transform: translate(-100px, -80px);
background-position: top left;
}
}
...;
}
- Add it to the imports folder and import it back into
styles.scss
with:
@import 'imports/header';
Nest the media query for the nav:
nav {
background: var(--light-gray);
border-top: 0.5rem solid var(--light-orange);
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
@media (min-width: $break-two) {
grid-area: nav;
}
...;
}
Finally, create partials _base.scss
and _content.scss
.
In _base.scss
:
@import url(futura/stylesheet.css);
html {
--basil-green: #88a308;
--dark-gray: #333333;
--light-gray: #e4e1d1;
--light-green: #f5faef;
--orange: #f90;
--light-orange: #ebbd4e;
--red: #f00;
--max-width: 840px;
--breakpoint: 640px;
}
* {
margin: 0;
padding: 0;
}
::selection {
background: var(--basil-green);
color: #fff;
}
body {
font: 100%/1.5 'Segoe UI', Candara, 'Bitstream Vera Sans', 'DejaVu Sans', 'Bitstream Vera Sans',
'Trebuchet MS', Verdana, 'Verdana Ref', sans-serif;
color: var(--dark-gray);
max-width: var(--max-width);
@media (min-width: $break-two) {
margin: 0 auto;
margin-top: 1.5rem;
display: grid;
grid-template-areas:
'header'
'nav'
'content'
'footer';
}
}
h2,
h3 {
color: var(--basil-green);
margin: 8px 0;
font-size: 1.4rem;
letter-spacing: -1px;
}
h2 {
font-size: 2rem;
}
a {
color: var(--orange);
text-decoration: none;
transition: color 0.5s linear;
}
li > h4 {
margin-top: 12px;
}
img {
width: 100%;
height: auto;
}
In _content.scss
.content {
@media (min-width: $break-two) {
grid-area: content;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-column-gap: 1rem;
}
a:hover {
color: var(--basil-green);
}
}
article {
padding: 1rem;
@media (min-width: $break-two) {
grid-column: span 3;
}
li {
margin-left: 1rem;
margin-bottom: 0.5rem;
}
ol {
margin-left: 1rem;
margin-bottom: 0.5rem;
}
figcaption {
font-size: 0.75rem;
}
}
aside {
padding: 1rem;
@media (min-width: $break-two) {
grid-column: span 2;
background: var(--light-green);
box-shadow: -4px 0px 4px #ddd;
}
li {
list-style: none;
margin-left: 1rem;
margin-bottom: 0.5rem;
}
}
footer {
background-color: var(--basil-green);
padding: 1rem;
border-radius: 0 0 4px 4px;
margin-bottom: 2rem;
@media (min-width: $break-two) {
grid-area: footer;
}
}
And in styles.scss
all you should need is:
@import 'imports/variables';
@import 'imports/base';
@import 'imports/header';
@import 'imports/nav';
@import 'imports/content';
Test in the browser at various sizes.
Note that we get a horizontal scrollbar at a 375px width. This is due to the size of the navigation buttons.
Try changing them in _nav.scss
:
a {
text-align: center;
font-size: 1.5rem;
padding: 8px;
min-width: 90px;
color: #fff;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
border-radius: 6px;
display: inline-block;
@media (min-width: $break-two) {
min-width: 120px;
}
}
Note: switching branches at this point will delete styles.css from the css directory. Stop the processes running in the console before switching branches.
Finally, copy the font css into a new partial and change the paths:
@font-face {
font-family: 'FuturaStdLight';
src: url('futura/futurastd-light-webfont.eot');
src: url('futura/futurastd-light-webfont.eot?#iefix') format('embedded-opentype'),
url('futura/futurastd-light-webfont.woff') format('woff'),
url('futura/futurastd-light-webfont.ttf') format('truetype'), url('futura/futurastd-light-webfont.svg#FuturaStdLight')
format('svg');
font-weight: normal;
font-style: normal;
}
Once you have everything visually working and start using your template with live data you typically find issues with the design.
Note index.js
in the js
folder and link it to index.html
:
<script src="js/index.js"></script>
var API = 'https://ron-swanson-quotes.herokuapp.com/v2/quotes'
fetch(API)
.then(function (response) {
return response.json()
})
.then(function (data) {
document.querySelector('.quote').innerText = data[0]
})
Add class "quote" to the lone paragraph in the navigation. Use aria-live="polite"
Let's ease into JavaScript with a demonstration and a simple DOM manipulation.
Review Node:
$ mkdir node
$ cd node
$ touch basilnode.js
$ npm init -y
$ npm install random-number
Random Number on npmjs.com.
In basilnode.js
:
const randomNumber = require('random-number')
const randomIndex = randomNumber({
min: 0,
max: 4,
integer: true,
})
console.log(randomIndex)
console.log(typeof randomNumber)
console.log(typeof randomIndex)
At the command line:
$ node basilnode.js
Add some additional variables - arrays:
const randomNumber = require('random-number')
const basilChef = ['mama', 'papa', 'baby']
function randomItem(array) {
const randomIndex = randomNumber({
min: 0,
max: array.length - 1,
integer: true,
})
return array[randomIndex]
}
console.log(basilChef)
console.log(basilChef[0])
console.log(basilChef.length)
console.log(randomItem(basilChef))
$ node basilnode.js
Call the randomItem function from within another function and use a template string to construct a bit of HTML::
const randomNumber = require('random-number')
const basilChef = ['mama', 'papa', 'baby']
const basilTexture = ['greasy', 'frozen', 'spicy']
function randomItem(array) {
const randomIndex = randomNumber({
min: 0,
max: array.length - 1,
integer: true,
})
return array[randomIndex]
}
function makeBasil() {
return `<h2>${randomItem(basilChef)}'s ${randomItem(basilTexture)} basil</h2>`
}
console.log(makeBasil())
We'll do something similar to the node demo above in our app - replacing the recipe title with a random one.
In index.js
:
Evolve a function that uses JavaScript's built-in Math methods to return a random number between zero and two:
function random() {
const max = 3
// const randomIndex = Math.random();
// const randomIndex = Math.random() * max;
const randomIndex = Math.floor(Math.random() * max)
return randomIndex
}
console.log(random())
Now call our random function passing in an array.
const basilChefs = ['mama', 'papa', 'baby']
function random(array) {
const max = array.length
const randomIndex = Math.floor(Math.random() * max)
return array[randomIndex]
}
var name = random(basilChefs)
console.log(name)
We used the random number to select a name from the array and return it to the calling function.
Add another variable basilTexture
and massage the output to product a string:
const basilChefs = ['mama', 'papa', 'baby']
const basilTexture = ['greasy', 'frozen', 'spicy']
function random(array) {
const max = array.length
const randomIndex = Math.floor(Math.random() * max)
return array[randomIndex]
}
var recipeName =
'My ' + random(basilChefs) + "'s " + random(basilTexture) + ' pesto'
console.log(recipeName)
Let's use the return value in our layout:
const el = document.querySelector('h2')
Test el
in the console.
const el = document.querySelector('h2')
const basilChefs = ['mama', 'papa', 'baby']
const basilTexture = ['greasy', 'frozen', 'spicy']
function random(array) {
const max = array.length
const randomIndex = Math.floor(Math.random() * max)
return array[randomIndex]
}
var recipeName = `${random(basilChefs)}'s ${random(basilTexture)} pesto`
el.innerHTML = recipeName
and format it in _base.scss
:
h2 {
font-size: 2rem;
text-transform: capitalize;
}
Create a div on the bottom of the html page (but before the script tag).
<div class="modal">
<h3>Hi! I'm a Modal Window (ʘ‿ʘ)╯</h3>
<p>Information about the beta program.</p>
</div>
Create _modal.scss
and add it to styles.scss
(e.g. @import 'imports/modal';
):
.modal {
max-width: 600px;
min-width: 400px;
padding: 2rem;
border-radius: 5px;
min-height: 200px;
border: 2px solid var(--orange);
background: white;
position: fixed;
top: 30%;
left: calc(50% - 150px);
// display: none;
}
Uncomment display: none
and add a open
rule:
.open {
display: block;
}
Test by adding the open
class to the modal using the dev tool's inspector.
Code the .beta
button to show the window.
Create a variable for the beta button, attach an event listener to it, and create a function to handle the event.
var modal = document.querySelector('.modal')
var betaButton = document.querySelector('.beta')
function showPopover(event) {
modal.classList.toggle('open')
event.preventDefault()
}
betaButton.addEventListener('click', showPopover)
Refactor to use event delegation:
var modal = document.querySelector('.modal')
// var betaButton = document.querySelector('.beta');
function showPopover(event) {
console.log(event.target)
if (!event.target.matches('.beta')) return
modal.classList.toggle('open')
event.preventDefault()
}
// betaButton.addEventListener('click', showPopover);
document.addEventListener('click', showPopover)
- Use querySelector to find the first matching element on a page
var modal = document.querySelector('.modal');
- Use querySelectorAll() to find all matching elements on a page
- Use addEventListener('event', function), to listen for events on an element. You can find a full list of available events on the Mozilla Developer Network
- Use Functions to store and execute your commands
- Use classList to add, remove, toggle, list and test for classes
The matches()
method lets you check if an element would be selected by a particular selector. It returns true if the element is a match, and false when it’s not. It can be an alternative to using element.classList.contains('.someclass')
.
// Match by an ID
if (elem.matches('#first-button')) {
// Do something...
}
// Match by a class
if (elem.matches('.button-submit')) {
// Do something...
}
// Match by one of several selectors
// Returns true when element contains at least one of the selectors
if (elem.matches('.click-me, .button-submit')) {
// Do something...
}
Add html to the betainfo:
<div class="modal">
<h3>Hi! I'm a Modal Window (ʘ‿ʘ)╯</h3>
<p>Information about the beta program.</p>
<!-- NEW -->
<a class="closer" href="#0">✖︎</a>
</div>
Style it:
.closer {
position: absolute;
top: -10px;
right: -10px;
width: 1.5rem;
height: 1.5rem;
background: #fff;
color: var(--orange);
border: 4px solid var(--orange);
border-radius: 50%;
text-align: center;
line-height: 1.5rem;
cursor: pointer;
}
Extend the showPopover function to include the new element script.
var modal = document.querySelector('.modal')
function showPopover(event) {
if (!event.target.matches('.beta, .closer')) return
modal.classList.toggle('open')
event.preventDefault()
}
document.addEventListener('click', showPopover)
Note: you cannot animate between display: none
and display: block
.
Add a wrapping div - modal-outer
- around the modal:
<div class="modal-outer">
<div class="modal">
<h3>Hi! I'm a Modal Window (ʘ‿ʘ)╯</h3>
<p>Information about the beta program.</p>
<a class="closer" href="#0">✖︎</a>
</div>
</div>
Style it:
.modal-outer {
display: grid;
background: rgba(0, 0, 0, 0.5);
position: fixed;
height: 100vh;
width: 100vw;
top: 0;
left: 0;
justify-content: center;
align-items: center;
/* Hide this until we need it */
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.modal-outer.open {
opacity: 1;
pointer-events: all;
}
Try: changing the opacity and pointer-events properties to 1 and all.
Edit the script to select the outer div and apply the open
class to it:
var modal = document.querySelector('.modal')
var modalOuter = document.querySelector('.modal-outer')
function showPopover(event) {
if (!event.target.matches('.beta, .closer')) return
modalOuter.classList.toggle('open')
event.preventDefault()
}
document.addEventListener('click', showPopover)
Now the modal wrapper will show when the button is clicked - but the modal will not.
Edit styles for the interior modal:
.modal {
max-width: 600px;
min-width: 400px;
padding: 2rem;
border-radius: 5px;
min-height: 200px;
border: 2px solid var(--orange);
background: white;
transform: translateY(200%);
transition: transform 1s;
}
.modal-outer.open .modal {
transform: translateY(0);
}
Note that we are no longer using 'display: none
to hide the modal. The inner modal is becoming visible because its container, modal outer, is transitioning opacity.
Edit the script to allow clicking on the overlay to close the modal.
var modalOuter = document.querySelector('.modal-outer')
function showPopover(event) {
if (event.target.matches('.beta')) {
modalOuter.classList.add('open')
} else if (event.target.matches('.closer, .modal-outer')) {
modalOuter.classList.remove('open')
} else return
event.preventDefault()
}
document.addEventListener('click', showPopover)
We will use the popover for different purposes depending on which element is clicked.
var modalOuter = document.querySelector('.modal-outer')
var modalInner = document.querySelector('.modal')
var betaContent = `
<h3>Oooops!</h3>
<p>Wow! Nothing works!<p>
`
function showPopover(event) {
if (event.target.matches('.beta')) {
modalInner.innerHTML = betaContent
modalOuter.classList.add('open')
} else if (event.target.matches('.closer, .modal-outer')) {
modalOuter.classList.remove('open')
} else return
event.preventDefault()
}
document.addEventListener('click', showPopover)
Let's use our new popover to display a different message when the user clicks on any of the three nav buttons.
var modalOuter = document.querySelector('.modal-outer')
var modalInner = document.querySelector('.modal')
var betaContent = `
<h3>Oooops!</h3>
<p>Wow! Nothing works!<p>
`
var buttonContent = `
<h2>Coming Soon</h2>
<p>This feature coming soon.<p>
<a class="closer" href="#0">✖︎</a>
`
function showPopover(event) {
if (event.target.matches('.beta')) {
modalInner.innerHTML = betaContent
modalOuter.classList.add('open')
} else if (event.target.closest('nav ul')) {
modalInner.innerHTML = buttonContent
modalOuter.classList.add('open')
} else if (event.target.matches('.closer, .modal-outer')) {
modalOuter.classList.remove('open')
} else return
event.preventDefault()
}
document.addEventListener('click', showPopover)
Note the use of closest above. The closest()
method looks for the closest matching parent to an element that has a selector that you pass in.
Template literals allow embedded expressions. You can use multi-line strings and string interpolation features with them. They were called "template strings" in prior editions of the ES2015 specification.
let recipe = `
<figure>
<picture>
<img src="img/pesto.jpg" alt="Italian pesto" />
</picture>
<figcaption>
Classic, simple basil pesto recipe with fresh basil leaves, pine
nuts, garlic, Romano or Parmesan cheese, extra virgin olive oil, and
salt and pepper.
</figcaption>
</figure>
<h2 itemprop="name">Pesto</h2>
<p itemprop="description">
A sauce of crushed basil leaves, pine nuts, garlic, Parmesan cheese,
and olive oil, typically served with pasta.
</p>
<h3>Directions</h3>
<ol itemprop="recipeInstructions">
<li>
Combine the basil, garlic, and pine nuts in a food processor and
pulse until coarsely chopped. Add 1/2 cup of the oil and process
until fully incorporated and smooth. Season with salt and pepper.
</li>
<li>
If using immediately, add all the remaining oil and pulse until
smooth. Transfer the pesto to a large serving bowl and mix in the
cheese.
</li>
<li>
If freezing, transfer to an air-tight container and drizzle
remaining oil over the top. Freeze for up to 3 months. Thaw and stir
in cheese.
</li>
</ol>
<h3>Ingredients</h3>
<ul>
<li itemprop="recipeIngredient">2 cups packed fresh basil leaves</li>
<li itemprop="recipeIngredient">2 cloves garlic</li>
<li itemprop="recipeIngredient">1/4 cup pine nuts</li>
<li itemprop="recipeIngredient">2/3 cup extra-virgin olive oil</li>
<li itemprop="recipeIngredient">
Kosher salt and freshly ground black pepper, to taste
</li>
<li itemprop="recipeIngredient">
1/2 cup freshly grated Pecorino cheese
</li>
<li itemprop="recipeIngredient">
1 <abbr title="Pounds">lb</abbr> plain pasta
</li>
</ul>
`
const article = document.querySelector('article')
article.innerHTML = recipe
Any unit of code that can be evaluated to a value is an expression. Since expressions produce values, they can appear anywhere in a program where JavaScript expects a value.
10 + 13
'hello' + 'world'
A statement is an instruction to perform a specific action - creating a variable or a function, looping through an array of elements, and evaluating code based on a specific condition.
var total = 0
function greet(message) {
console.log(message)
}
let dirs = ''
function createDirections() {
for (let i = 0; i < currRecipe.directions.length; i++) {
dirs += '<li>' + currRecipe.directions[i] + '</li>'
}
}
createDirections()
${currRecipe.directions.map(dir => {
`<li>${dir}</li>`;
})}
${currRecipe.directions.map(dir => `<li>${dir}</li>`)}
${currRecipe.directions.map(dir => `<li>${dir}</li>`).join('')}
ONE
const recipeTitle = recipesData[0].name
console.log(recipeTitle)
const figure = document.querySelector('h2')
console.log(figure)
figure.innerText = recipeTitle
<div id="app"></div>
TWO
const recipe = recipesData[0]
const recipeOne = '<h2 itemprop="name">' + recipe.name + '</h2>'
const app = document.querySelector('#app')
app.innerHTML = recipeOne
<div id="app"></div>
THREE
const recipe = recipesData[0]
const recipeOne =
'<h2>' +
recipe.name +
'</h2>' +
'<figure>' +
'<picture>' +
'<img src=img/' +
recipe.photo +
' alt="' +
recipe.name +
'" />' +
'</picture>' +
'<figcaption>' +
recipe.description +
'</figcaption>' +
'</figure>'
const app = document.querySelector('#app')
app.innerHTML = recipeOne
FOUR
const recipe = recipesData[0]
const recipeOne = `<h2>${recipe.name}</h2>
<figure >
<picture>
<img src="img/${recipe.photo}" alt="${recipe.name}" />
</picture>
<figcaption>${recipe.description}</figcaption>
</figure>`
console.log(recipeOne)
const app = document.querySelector('#app')
app.innerHTML = recipeOne
Concurrently:
"start": "concurrently \"npm run sass\" \"npm run server \" "
"start": "npm run server & npm run sass"
"scripts": {
"server": "browser-sync start --directory --server 'app' --files 'app'",
"sass": "sass scss/styles.scss app/css/styles.css --watch --source-map",
"start": "concurrently \"npm run sass\" \"npm run server\" "
},
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules