Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00622

UC00622 · Criar aplicações em HTML5

HTML, CSS, JavaScript moderno, responsive
25h · 2.25 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

Introdução

HTML5 é a base de toda a web moderna. Esta UC (50h) cobre: - HTML5: markup semântico. - CSS3: design e layout (Flexbox, Grid). - JavaScript moderno (ES6+): interactividade. - Responsive design: mobile-first. - Acessibilidade e SEO básico.

Foco: construir sites reais desde o primeiro dia, com práticas profissionais.


1. Web: como funciona

1.1 Arquitectura

Browser (cliente) ──HTTP request──► Servidor
   ▲                                    │
   │                                    │
   └─HTML/CSS/JS+assets──────────────────┘

1.2 Tecnologias web

Princípio: separação de preocupações.

1.3 Setup

Editor: VSCode + extensões: - Live Server. - Prettier. - Auto Rename Tag.

Browser: Chrome / Firefox com DevTools (F12).

Servidor local:

# Python
python -m http.server 8000

# Node
npx serve

# VSCode: Live Server extension (Alt+L Alt+O)

2. HTML5

2.1 Estrutura base

<!DOCTYPE html>
<html lang="pt-PT">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Título do site</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>Conteúdo principal</h1>
  <script src="script.js"></script>
</body>
</html>

Sempre incluir: - <!DOCTYPE html> (modo standards). - lang attribute. - charset UTF-8. - viewport meta (responsive).

2.2 Tags semânticas HTML5

<header>     <!-- cabeçalho da página/secção -->
  <nav>...</nav>
</header>

<main>       <!-- conteúdo principal -->
  <article>  <!-- conteúdo independente -->
    <section>...</section>
  </article>

  <aside>    <!-- lateral / sidebar -->
  </aside>
</main>

<footer>     <!-- rodapé -->
</footer>

Por que semântica: - Acessibilidade: leitores de ecrã percebem estrutura. - SEO: motores de busca classificam melhor. - Manutenção: código mais legível. - CSS: targetable sem classes extras.

Em vez de:

<div class="header">
  <div class="nav">...</div>
</div>

Fazer:

<header>
  <nav>...</nav>
</header>

2.3 Tags de conteúdo

Headings:

<h1>Título principal</h1>     <!-- 1 por página -->
<h2>Secção</h2>
<h3>Subsecção</h3>
<!-- até h6 -->

Texto:

<p>Parágrafo</p>
<strong>Importante</strong>
<em>Ênfase</em>
<small>Texto pequeno</small>
<mark>Destacado</mark>
<code>código</code>
<pre>texto pré-formatado</pre>
<blockquote>Citação</blockquote>

Listas:

<!-- Não ordenada -->
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
</ul>

<!-- Ordenada -->
<ol>
  <li>Primeiro</li>
  <li>Segundo</li>
</ol>

<!-- Definições -->
<dl>
  <dt>HTML</dt>
  <dd>HyperText Markup Language</dd>
</dl>

Links:

<a href="https://example.com">Link externo</a>
<a href="/sobre">Link interno</a>
<a href="#seccao">Âncora</a>
<a href="mailto:x@y.pt">Email</a>
<a href="tel:+351200000000">Telefone</a>

<!-- Abrir em nova aba -->
<a href="..." target="_blank" rel="noopener noreferrer">

Imagens:

<img src="foto.jpg" alt="Descrição da imagem">

<!-- Com srcset para responsive -->
<img srcset="small.jpg 480w,
             large.jpg 1200w"
     sizes="(max-width: 600px) 480px, 1200px"
     src="large.jpg"
     alt="Descrição">

<!-- Picture (formats alternativos) -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg">
  <img src="image.jpg" alt="">
</picture>

alt é obrigatório (acessibilidade + SEO).

2.4 Mídia

Vídeo:

<video src="video.mp4" controls width="600">
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
  O teu browser não suporta vídeo.
</video>

Áudio:

<audio src="musica.mp3" controls>
  O teu browser não suporta áudio.
</audio>

Embed (YouTube):

<iframe src="https://www.youtube.com/embed/VIDEOID"
        width="560" height="315"
        frameborder="0" allowfullscreen></iframe>

2.5 Formulários

<form action="/submit" method="POST">
  <!-- Text -->
  <label for="nome">Nome:</label>
  <input type="text" id="nome" name="nome" required>

  <!-- Email -->
  <label for="email">Email:</label>
  <input type="email" id="email" name="email" 
         placeholder="exemplo@email.com" required>

  <!-- Password -->
  <label for="pass">Password:</label>
  <input type="password" id="pass" name="pass" 
         minlength="8" required>

  <!-- Number -->
  <input type="number" min="0" max="100" step="1">

  <!-- Date -->
  <input type="date" min="2026-01-01">

  <!-- Checkbox -->
  <input type="checkbox" id="newsletter" name="newsletter">
  <label for="newsletter">Subscrever newsletter</label>

  <!-- Radio -->
  <input type="radio" id="m" name="genero" value="M">
  <label for="m">Masculino</label>
  <input type="radio" id="f" name="genero" value="F">
  <label for="f">Feminino</label>

  <!-- Select -->
  <label for="pais">País:</label>
  <select id="pais" name="pais">
    <option value="">--Selecciona--</option>
    <option value="PT">Portugal</option>
    <option value="ES">Espanha</option>
  </select>

  <!-- Textarea -->
  <label for="msg">Mensagem:</label>
  <textarea id="msg" name="msg" rows="5"></textarea>

  <!-- Submit -->
  <button type="submit">Enviar</button>
  <button type="reset">Limpar</button>
</form>

Input types HTML5: text, email, password, number, range, date, time, datetime-local, color, file, tel, url, search.

Validation atributos: - required: obrigatório. - min, max: número/data. - minlength, maxlength: texto. - pattern: regex. - placeholder: dica.

2.6 Tabelas

<table>
  <caption>Vendas mensais</caption>
  <thead>
    <tr>
      <th>Mês</th>
      <th>Vendas (€)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Janeiro</td>
      <td>5,000</td>
    </tr>
    <tr>
      <td>Fevereiro</td>
      <td>6,500</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td>Total</td>
      <td>11,500</td>
    </tr>
  </tfoot>
</table>

Tabelas apenas para dados tabulares, NÃO para layout (use CSS Grid).


3. CSS

3.1 Como aplicar

3 formas:

1. Externo (recomendado):

<link rel="stylesheet" href="style.css">

2. Interno:

<head>
  <style>
    h1 { color: blue; }
  </style>
</head>

3. Inline (evitar):

<h1 style="color: blue;">Título</h1>

3.2 Sintaxe

/* Comentário */

selector {
  propriedade: valor;
  outra-propriedade: outro-valor;
}

/* Múltiplos selectors */
h1, h2, h3 {
  font-family: 'Inter', sans-serif;
}

3.3 Selectors

Básicos:

/* Elemento */
p { color: black; }

/* Classe */
.destaque { background: yellow; }

/* ID */
#header { height: 80px; }

/* Universal */
* { box-sizing: border-box; }

Combinadores:

/* Descendente (qualquer nível) */
nav a { color: white; }

/* Filho directo */
nav > a { color: blue; }

/* Irmão adjacente */
h1 + p { font-size: 18px; }

/* Irmãos gerais */
h1 ~ p { color: gray; }

Pseudo-classes:

a:hover { color: red; }
a:focus { outline: 2px solid blue; }
a:active { color: purple; }
a:visited { color: gray; }

input:disabled { opacity: 0.5; }
input:checked + label { font-weight: bold; }

li:first-child { font-weight: bold; }
li:last-child { border-bottom: none; }
li:nth-child(odd) { background: #f5f5f5; }
li:nth-child(2n+1) { /* ímpares */ }

Pseudo-elementos:

p::first-line { font-weight: bold; }
p::first-letter { font-size: 2em; }
p::before { content: "→ "; }
p::after { content: " ✓"; }

Attribute selectors:

input[type="email"] { border: 1px solid blue; }
a[href^="https://"] { color: green; }
a[href$=".pdf"]::after { content: " 📄"; }

3.4 Especificidade

Pesos: - Inline style: 1000. - ID: 100. - Classe / pseudo-classe / atributo: 10. - Elemento / pseudo-elemento: 1.

Exemplos: - p = 1. - .botao = 10. - #header .botao = 110. - body p.destaque = 12.

Mais específico vence.

!important força (evitar, usar com extrema cautela):

p { color: red !important; }

3.5 Box model

Cada elemento é uma "caixa":

┌─────────────────────────────┐
│       margin                 │
│  ┌───────────────────────┐  │
│  │      border           │  │
│  │  ┌─────────────────┐  │  │
│  │  │     padding      │  │  │
│  │  │  ┌───────────┐  │  │  │
│  │  │  │  content   │  │  │  │
│  │  │  └───────────┘  │  │  │
│  │  └─────────────────┘  │  │
│  └───────────────────────┘  │
└─────────────────────────────┘

Propriedades:

.box {
  /* Conteúdo */
  width: 300px;
  height: 200px;

  /* Padding (interno) */
  padding: 20px;
  padding: 10px 20px;          /* top/bottom right/left */
  padding: 10px 20px 30px 40px; /* top right bottom left */

  /* Border */
  border: 1px solid black;
  border-radius: 8px;

  /* Margin (externo) */
  margin: 0 auto;  /* centrar horizontalmente */
}

/* Recomendado para todos: */
* {
  box-sizing: border-box;
}

border-box: width = content + padding + border (mais intuitivo).

3.6 Cores

/* Hexadecimal */
color: #ff5733;
color: #f53;  /* short */

/* RGB / RGBA */
color: rgb(255, 87, 51);
color: rgba(255, 87, 51, 0.5);  /* 50% transparente */

/* HSL */
color: hsl(11, 100%, 60%);

/* Named */
color: red;
color: rebeccapurple;

/* CSS Variables */
:root {
  --primary: #6366f1;
  --secondary: #f59e0b;
}

.button {
  background: var(--primary);
}

3.7 Tipografia

body {
  /* Fonte */
  font-family: 'Inter', -apple-system, sans-serif;

  /* Tamanho */
  font-size: 16px;   /* px (fixo) */
  font-size: 1rem;   /* rem (relativo a root) */
  font-size: 1.2em;  /* em (relativo ao parent) */

  /* Peso */
  font-weight: 400;  /* normal */
  font-weight: 700;  /* bold */

  /* Estilo */
  font-style: italic;

  /* Espaçamento */
  line-height: 1.6;        /* altura das linhas */
  letter-spacing: 0.05em;  /* entre letras */
  word-spacing: 0.1em;     /* entre palavras */

  /* Alinhamento */
  text-align: center;
  text-align: justify;

  /* Decoração */
  text-decoration: underline;
  text-transform: uppercase;
}

Web fonts (Google Fonts):

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
body {
  font-family: 'Inter', sans-serif;
}

3.8 Background

.hero {
  background-color: #f0f0f0;
  background-image: url('bg.jpg');
  background-size: cover;       /* fill area */
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed;  /* parallax */

  /* Shorthand */
  background: url('bg.jpg') no-repeat center/cover;

  /* Gradient */
  background: linear-gradient(135deg, #667eea, #764ba2);
  background: radial-gradient(circle, red, blue);
}

4. CSS avançado

4.1 Flexbox

Layout 1D (linha ou coluna):

.container {
  display: flex;
  flex-direction: row;          /* row, column, row-reverse */
  justify-content: center;       /* eixo principal */
  align-items: center;           /* eixo cruzado */
  gap: 20px;                     /* espaço entre items */
  flex-wrap: wrap;               /* quebrar linha */
}

.item {
  flex: 1;                       /* cresce igualmente */
  flex: 0 0 200px;               /* fixo 200px */
  flex-basis: 300px;
  flex-grow: 1;
  flex-shrink: 0;
}

Valores justify-content: - flex-start, flex-end, center. - space-between, space-around, space-evenly.

Valores align-items: - stretch (default), flex-start, flex-end, center, baseline.

Casos comuns:

Centrar elemento:

.parent {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

Navbar:

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
}

Cards row:

.cards {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.card {
  flex: 1 1 250px;  /* min 250px, cresce */
}

4.2 Grid

Layout 2D:

.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;        /* 3 colunas iguais */
  grid-template-columns: repeat(3, 1fr);      /* shortcut */
  grid-template-columns: 200px 1fr 200px;    /* sidebar + main + sidebar */
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));  /* responsive */

  grid-template-rows: auto;

  gap: 20px;                       /* entre cells */
  gap: 20px 10px;                  /* row / column */
}

/* Posicionar items */
.item {
  grid-column: 1 / 3;              /* coluna 1 a 3 */
  grid-column: span 2;             /* span 2 colunas */

  grid-row: 1;
  grid-row: 1 / span 2;
}

Areas (mais legível):

.container {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  min-height: 100vh;
}

header { grid-area: header; }
.sidebar { grid-area: sidebar; }
main { grid-area: main; }
footer { grid-area: footer; }

4.3 Transitions e animations

Transitions (mudanças suaves):

.button {
  background: blue;
  transition: background 0.3s ease;
}

.button:hover {
  background: red;
}

/* Multiple */
.button {
  transition: background 0.3s, transform 0.2s ease-out;
}

Transform:

.box {
  transform: translateX(100px);
  transform: translateY(-50%);
  transform: scale(1.5);
  transform: rotate(45deg);
  transform: skew(10deg);

  /* Combinadas */
  transform: translate(100px, 50px) rotate(45deg) scale(1.2);
}

Animations (keyframes):

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.element {
  animation: slideIn 0.5s ease-out;
  animation: slideIn 0.5s ease-out forwards;  /* fica no estado final */
}

4.4 CSS Variables (Custom Properties)

:root {
  --primary: #6366f1;
  --secondary: #f59e0b;
  --text: #0f172a;
  --bg: #ffffff;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 2rem;
}

.button {
  background: var(--primary);
  color: white;
  padding: var(--space-1) var(--space-2);
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --text: #ffffff;
    --bg: #0f172a;
  }
}

4.5 Responsive design

Mobile-first (recomendado):

/* Base (mobile) */
.container {
  display: flex;
  flex-direction: column;
  padding: 1rem;
}

/* Tablet (>= 768px) */
@media (min-width: 768px) {
  .container {
    flex-direction: row;
    padding: 2rem;
  }
}

/* Desktop (>= 1024px) */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
  }
}

Breakpoints típicos: - Mobile: < 768px. - Tablet: 768-1023px. - Desktop: 1024-1439px. - Large: 1440px+.

Unidades responsivas: - vw/vh: viewport width/height (1vw = 1% viewport). - %: % do parent. - rem: relativo ao root font-size. - em: relativo ao parent. - clamp(min, ideal, max): limites.

.title {
  font-size: clamp(1.5rem, 5vw, 3rem);
}

5. JavaScript fundamentos

5.1 Hello World

<!-- Inline -->
<script>
  console.log("Olá!");
</script>

<!-- Externo (recomendado) -->
<script src="script.js"></script>

<!-- Defer (depois de DOM carregar) -->
<script src="script.js" defer></script>

<!-- Async (paralelo) -->
<script src="script.js" async></script>

<!-- Module (ES6+) -->
<script type="module" src="app.js"></script>

5.2 Variáveis

// const (preferido, não pode reatribuir)
const PI = 3.14;
const nome = "Ana";

// let (pode reatribuir)
let contador = 0;
contador++;

// var (legacy, evitar)

// Tipos primitivos
const numero = 42;
const decimal = 3.14;
const texto = "Olá";
const booleano = true;
const indefinido = undefined;
const nulo = null;
const simbolo = Symbol("id");
const grande = 9007199254740992n;  // BigInt

// Objectos
const array = [1, 2, 3];
const objecto = { nome: "Ana", idade: 25 };
const data = new Date();

5.3 Operadores

// Aritméticos
5 + 3   // 8
5 - 3   // 2
5 * 3   // 15
5 / 3   // 1.6666...
5 % 3   // 2 (resto)
5 ** 3  // 125 (potência)

// Comparação
==    // igual (com coercion — evitar)
===   // igual estrito (preferir)
!=    // diferente
!==   // diferente estrito
< > <= >=

// Lógicos
&& || !

// Ternário
const status = idade >= 18 ? "adulto" : "menor";

// Nullish coalescing
const valor = input ?? "default";  // se null/undefined, "default"

// Optional chaining
user?.address?.city  // não dá erro se intermediário é null

5.4 Strings

const nome = "Python";

// Métodos
nome.length              // 6
nome.toUpperCase()       // "PYTHON"
nome.toLowerCase()       // "python"
nome.indexOf("th")       // 2
nome.includes("th")      // true
nome.slice(0, 3)         // "Pyt"
nome.split("")           // ["P","y","t","h","o","n"]
nome.replace("Py", "Co") // "Cothon"
nome.trim()              // remove whitespace

// Template literals (ES6+)
const idade = 25;
const msg = `${nome} tem ${idade} anos`;

// Multi-linha
const html = `
  <div>
    <h1>${nome}</h1>
  </div>
`;

5.5 Arrays

const frutas = ["maçã", "banana", "uva"];

// Acesso
frutas[0]            // "maçã"
frutas[frutas.length - 1]  // "uva"

// Adicionar / remover
frutas.push("kiwi")        // adicionar no fim
frutas.pop()                // remover último
frutas.unshift("manga")    // adicionar no início
frutas.shift()              // remover primeiro

// Métodos modernos (funcionais)
const numeros = [1, 2, 3, 4, 5];

numeros.map(x => x * 2)              // [2, 4, 6, 8, 10]
numeros.filter(x => x > 2)            // [3, 4, 5]
numeros.reduce((acc, x) => acc + x, 0)  // 15

numeros.find(x => x > 3)              // 4
numeros.some(x => x > 4)              // true
numeros.every(x => x > 0)             // true

numeros.forEach(x => console.log(x))

// Sort
numeros.sort((a, b) => a - b)         // ascendente
numeros.sort((a, b) => b - a)         // descendente

// Spread / rest
const a = [1, 2, 3];
const b = [...a, 4, 5];               // [1, 2, 3, 4, 5]

// Destructuring
const [first, second, ...rest] = numeros;

5.6 Objects

const pessoa = {
  nome: "Ana",
  idade: 25,
  saudar() {
    return `Olá, sou ${this.nome}`;
  }
};

// Acesso
pessoa.nome
pessoa["nome"]

// Modificar
pessoa.idade = 26;
pessoa.email = "ana@x.pt";

// Destructuring
const { nome, idade } = pessoa;
const { nome: n, idade: i } = pessoa;  // rename

// Spread
const novo = { ...pessoa, cidade: "Lisboa" };

// Iterar
Object.keys(pessoa)          // ["nome", "idade", "saudar"]
Object.values(pessoa)
Object.entries(pessoa)       // [["nome", "Ana"], ...]

for (const [chave, valor] of Object.entries(pessoa)) {
  console.log(chave, valor);
}

5.7 Funções

// Function declaration
function saudar(nome) {
  return `Olá, ${nome}`;
}

// Function expression
const saudar = function(nome) {
  return `Olá, ${nome}`;
};

// Arrow function (preferido em código moderno)
const saudar = (nome) => `Olá, ${nome}`;
const dobro = x => x * 2;  // 1 arg, sem parêntesis

const log = () => {
  console.log("sem args");
};

// Default params
const greet = (name = "Mundo") => `Hello, ${name}`;

// Rest params
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
sum(1, 2, 3, 4)  // 10

// Destructuring params
const print = ({ name, age }) => console.log(`${name}, ${age}`);
print({ name: "Ana", age: 25 });

5.8 Control flow

// If
if (idade < 18) {
  console.log("menor");
} else if (idade < 65) {
  console.log("adulto");
} else {
  console.log("sénior");
}

// Switch
switch (cor) {
  case "vermelho":
    console.log("stop");
    break;
  case "verde":
    console.log("go");
    break;
  default:
    console.log("?");
}

// For
for (let i = 0; i < 10; i++) {
  console.log(i);
}

// For of (arrays, strings, iterables)
for (const fruta of frutas) {
  console.log(fruta);
}

// For in (chaves de objects — cuidado)
for (const chave in pessoa) {
  console.log(chave, pessoa[chave]);
}

// While
let i = 0;
while (i < 5) {
  console.log(i++);
}

// Try / catch
try {
  JSON.parse(invalido);
} catch (error) {
  console.error("Erro:", error.message);
} finally {
  console.log("sempre corre");
}

6. DOM manipulation

6.1 Seleccionar elementos

// Por ID
const titulo = document.getElementById("titulo");

// Por classe (HTMLCollection)
const items = document.getElementsByClassName("item");

// Por tag (HTMLCollection)
const ps = document.getElementsByTagName("p");

// Query selector (CSS selector, moderno e preferido)
const primeiro = document.querySelector(".item");  // primeiro
const todos = document.querySelectorAll(".item");  // NodeList (forEach OK)

// Selectors complexos
const navLink = document.querySelector("nav > ul > li > a");

6.2 Modificar conteúdo

// Texto
elemento.textContent = "Novo texto";  // só texto, seguro
elemento.innerText = "Texto";          // texto visível
elemento.innerHTML = "<b>HTML</b>";    // parse HTML (cuidado: XSS)

// Atributos
elemento.setAttribute("id", "novo");
elemento.getAttribute("href");
elemento.removeAttribute("disabled");

// Propriedades
elemento.id = "novo";
elemento.className = "destaque";
elemento.href = "https://...";

// Data attributes
elemento.dataset.userId = "123";  // <div data-user-id="123">
console.log(elemento.dataset.userId);

6.3 Classes

// classList API
elemento.classList.add("active");
elemento.classList.remove("hidden");
elemento.classList.toggle("expanded");
elemento.classList.contains("active");  // boolean
elemento.classList.replace("old", "new");

6.4 Estilos

// Direto (inline)
elemento.style.color = "red";
elemento.style.backgroundColor = "yellow";  // camelCase
elemento.style.fontSize = "20px";

// Mais comum: alterar classe
elemento.classList.add("destaque");

/* CSS */
.destaque {
  color: red;
  background: yellow;
  font-size: 20px;
}

6.5 Criar/remover elementos

// Criar
const div = document.createElement("div");
div.textContent = "Novo!";
div.classList.add("box");

// Adicionar a parent
const container = document.getElementById("container");
container.appendChild(div);

// Inserir antes de outro
container.insertBefore(div, outroFilho);

// Métodos modernos (mais flexíveis)
container.append(div, "texto", outroEl);     // múltiplos, no fim
container.prepend(div);                       // no início
container.before(div);                        // como irmão anterior
container.after(div);                         // como irmão seguinte

// Remover
elemento.remove();

// Replace
elemento.replaceWith(novo);

6.6 Events

// addEventListener
botao.addEventListener("click", () => {
  console.log("Clicado!");
});

// Com evento como parâmetro
input.addEventListener("input", (e) => {
  console.log("Novo valor:", e.target.value);
});

// Múltiplos handlers
botao.addEventListener("click", handler1);
botao.addEventListener("click", handler2);

// Remover
botao.removeEventListener("click", handler1);

Eventos comuns: - Mouse: click, dblclick, mouseenter, mouseleave, mousemove. - Keyboard: keydown, keyup, keypress. - Form: submit, change, input, focus, blur. - Window: load, DOMContentLoaded, resize, scroll.

Form example:

const form = document.querySelector("form");

form.addEventListener("submit", (e) => {
  e.preventDefault();  // impedir refresh da página

  const formData = new FormData(form);
  const data = Object.fromEntries(formData);

  console.log("Form data:", data);

  // Validar
  if (!data.email.includes("@")) {
    alert("Email inválido!");
    return;
  }

  // Enviar (fetch)
  enviarDados(data);
});

6.7 Exemplo prático completo

<!DOCTYPE html>
<html lang="pt">
<head>
  <meta charset="UTF-8">
  <title>Contador</title>
  <style>
    body {
      font-family: sans-serif;
      text-align: center;
      padding: 2rem;
    }

    .contador {
      font-size: 4rem;
      font-weight: bold;
      color: #6366f1;
      margin: 2rem 0;
    }

    button {
      font-size: 1.2rem;
      padding: 0.5rem 1.5rem;
      margin: 0 0.5rem;
      cursor: pointer;
      border: none;
      border-radius: 8px;
      background: #6366f1;
      color: white;
      transition: background 0.2s;
    }

    button:hover {
      background: #4f46e5;
    }

    button.reset {
      background: #ef4444;
    }
  </style>
</head>
<body>
  <h1>Contador</h1>
  <div id="display" class="contador">0</div>

  <button id="dec"></button>
  <button id="inc">+</button>
  <button id="reset" class="reset">Reset</button>

  <script>
    let contador = 0;

    const display = document.getElementById("display");
    const btnInc = document.getElementById("inc");
    const btnDec = document.getElementById("dec");
    const btnReset = document.getElementById("reset");

    function actualizar() {
      display.textContent = contador;
    }

    btnInc.addEventListener("click", () => {
      contador++;
      actualizar();
    });

    btnDec.addEventListener("click", () => {
      contador--;
      actualizar();
    });

    btnReset.addEventListener("click", () => {
      contador = 0;
      actualizar();
    });
  </script>
</body>
</html>

7. Responsive design

(Coberto em CSS avançado. Recap aqui.)

7.1 Mobile first

/* Base = mobile */
.container {
  width: 100%;
  padding: 1rem;
}

/* Tablet+ */
@media (min-width: 768px) {
  .container {
    max-width: 750px;
    margin: 0 auto;
  }
}

/* Desktop+ */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;
    padding: 2rem;
  }
}

7.2 Viewport meta

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Obrigatório para responsive.

7.3 Imagens responsivas

<!-- Simples -->
<img src="foto.jpg" alt="" style="max-width: 100%; height: auto;">

<!-- srcset (browser escolhe) -->
<img srcset="small.jpg 480w,
             medium.jpg 800w,
             large.jpg 1200w"
     sizes="(max-width: 600px) 480px,
            (max-width: 900px) 800px,
            1200px"
     src="medium.jpg"
     alt="">

7.4 Mobile menu

<nav>
  <button class="menu-toggle" aria-label="Toggle menu"></button>
  <ul class="menu">
    <li><a href="#">Home</a></li>
    <li><a href="#">Sobre</a></li>
    <li><a href="#">Contacto</a></li>
  </ul>
</nav>

<script>
  const toggle = document.querySelector(".menu-toggle");
  const menu = document.querySelector(".menu");

  toggle.addEventListener("click", () => {
    menu.classList.toggle("open");
  });
</script>

<style>
  /* Mobile */
  .menu {
    display: none;
  }

  .menu.open {
    display: block;
  }

  /* Desktop */
  @media (min-width: 768px) {
    .menu-toggle {
      display: none;
    }

    .menu {
      display: flex;
      gap: 2rem;
    }
  }
</style>

8. Acessibilidade e SEO

8.1 Acessibilidade (a11y)

WCAG (Web Content Accessibility Guidelines).

Principais:

Imagens com alt:

<img src="foto.jpg" alt="Descrição da imagem">
<img src="decorativa.jpg" alt="">  <!-- decorativa -->

Labels em inputs:

<label for="nome">Nome:</label>
<input id="nome" type="text">

Headings hierárquicos:

<h1>Título único</h1>
<h2>Secção</h2>
<h3>Subsecção</h3>
<!-- Não saltar (não h1 → h3) -->

Contraste suficiente: WCAG AA = 4.5:1 para texto normal.

Navegação por teclado: Tab deve funcionar logicamente.

ARIA quando necessário:

<button aria-label="Fechar">×</button>
<nav aria-label="Principal">...</nav>
<div role="alert">Mensagem importante</div>

Ferramentas: - Lighthouse (Chrome DevTools): audit a11y. - axe DevTools (extensão). - WAVE.

8.2 SEO básico

Meta tags:

<head>
  <title>Título descritivo (50-60 chars)</title>
  <meta name="description" content="Descrição (150-160 chars)">

  <!-- Open Graph (Facebook, LinkedIn) -->
  <meta property="og:title" content="Título">
  <meta property="og:description" content="Descrição">
  <meta property="og:image" content="https://example.com/og.jpg">
  <meta property="og:url" content="https://example.com/page">

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image">

  <!-- Canonical (versão preferida) -->
  <link rel="canonical" href="https://example.com/page">
</head>

Semântica: - <header>, <nav>, <main>, <article>, <footer>. - 1 <h1> por página. - Hierarquia de headings.

URLs limpas: - /produtos/sapatos (bom). - /page.php?id=123&cat=4 (mau).

Performance: - Lighthouse score > 90. - Core Web Vitals: LCP, FID, CLS. - Imagens optimizadas (WebP). - Lazy loading: <img loading="lazy">.

Sitemap.xml e robots.txt:

/sitemap.xml  <- lista de páginas
/robots.txt   <- instruções para crawlers

9. Boas práticas

9.1 Organização

projecto/
├── index.html
├── css/
│   ├── reset.css
│   ├── style.css
│   └── responsive.css
├── js/
│   ├── main.js
│   └── utils.js
├── images/
└── fonts/

9.2 CSS

9.3 JavaScript

9.4 Performance

9.5 Cybersegurança


Apêndice A · Reset CSS

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  line-height: 1.5;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  color: #333;
  background: #fff;
}

img, picture, video, canvas, svg {
  display: block;
  max-width: 100%;
}

input, button, textarea, select {
  font: inherit;
}

a {
  color: inherit;
  text-decoration: none;
}

ul, ol {
  list-style: none;
}

Apêndice B · Glossário


Apêndice C · Recursos