2022 Safari & Web
WWDC22 · 32 min · Safari & Web
What’s new in Safari and WebKit
Explore the latest features in Safari and WebKit and learn how you can make better and more powerful websites. We’ll take you on a tour through the latest updates to HTML, CSS enhancements, Web Inspector tooling, Web APIs, and more.
Watch at developer.apple.com ↗Code shown on screen · 23 snippets
Dialog element
<!-- <dialog> element -->
<dialog method="dialog">
<form id="dialogForm">
<label for="givenName">Given name:</label>
<input class="focus" type="text" name="givenName">
<label for="familyName">Family name:</label>
<input class="focus" type="text" name="familyName">
<label>
<input type="checkbox"> Can trade in person
</label>
<button>Send</button>
</form>
</dialog> Backdrop pseudo-class
/* ::backdrop pseudo-element */
dialog::backdrop {
background: linear-gradient(rgba(233, 182, 76, 0.7), rgba(103, 12, 0, 0.6));
animation: fade-in 0.5s;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
} inert attribute
// inert attribute
function switchToIndex(index) {
this.items.forEach(item => item.inert = true);
this.items[index].inert = false;
this.currentIndex = index;
} Lazy image loading
<img src="images/shirt.jpg" loading="lazy"
alt="a brown polo shirt"
width="500" height="600"> Container Queries
/* Container queries */
.container {
container-type: inline-size;
container-name: clothing-card;
}
.content {
display: grid;
grid-template-rows: 1fr;
gap: 1rem;
}
@container clothing-card (width > 250px) {
.content {
grid-template-columns: 1fr 1fr;
}
/* additional layout code */
} Cascade layers
/* Author Styles - Layer A */
@layer utilities {
div {
background-color: red;
}
}
/* Author Styles - Layer B */
@layer customizations {
div {
background-color: teal;
}
}
/* Author Styles - Layer C */
@layer userDefaults {
div {
background-color: yellow;
}
} :has() pseudo-class
<!-- :has() pseudo-class -->
<style>
form:has(input[type="checkbox"]:checked) {
background: #ff927a;
}
</style>
<form class="message">
<textarea rows="5" cols="60" name="text"
placeholder="Enter text"></textarea>
<div class="checkbox">
<input type="checkbox" value="urgent">
<label>Urgent?</label>
</div>
<button>Send Message</button>
</form> Offset Path
/* offset-path */
:is(.blue, .teal, .yellow, .red) {
offset-path: circle(9vw at 5vw 50%);
}
@keyframes move {
100% {
offset-distance: 100%;
}
}
/* Animation */
.clothing-header.clicked :is(.blue, .teal, .red, .yellow) {
animation: move 1100ms ease-in-out;
} scroll-behavior: auto
html {
scroll-behavior: auto;
} scroll-behavior: smooth
html {
scroll-behavior: smooth;
} :focus-visible & accent-color
/* :focus-visible & accent-color */
:focus-visible {
outline: 4px solid var(--green);
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
}
:root {
accent-color: var(--green);
} Font palette dark mode & light mode
/* Dark mode */
font-palette: dark;
/* Light mode */
font-palette: light; Font palette custom colors
/* Dark mode */
font-palette: dark;
/* Light mode */
font-palette: light;
/* Custom colors */
@font-palette-values --MyPalette {
override-colors: 1 yellow;
}
#logo {
font-palette: --MyPalette;
} CSS Grid
/* Grid to layout cards */
main {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(225px, 1fr));
gap: 1rem;
}
/* Grid to layout each card’s content */
article {
display: grid;
grid-row: span 5;
} Adding sub grid
/* Grid to layout cards */
main {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(225px, 1fr));
gap: 1rem;
}
/* Grid to layout each card’s content */
article {
display: grid;
grid-row: span 5;
/* Adding subgrid, tying them together */
grid-template-rows: subgrid;
} Web App Manifest file icons
// Manifest file
"icons": [
{
"src": "orange-icon.png",
"sizes": "120x120",
"type": "image/png"
}
] apple-touch-icon
<!-- HTML head -->
<link rel="apple-touch-icon" href="blue-icon.png" /> Broadcast Channel
// State change
broadcastChannel.postMessage("Item is unavailable"); Origin private file system
// Accessing the origin private file system
const root = await navigator.storage.getDirectory();
// Create a file named Draft.txt under root directory
const draftHandle = await root.getFileHandle("Draft.txt", { "create": true });
// Access and read an existing file
const existingHandle = await root.getFileHandle("Draft.txt");
const existingFile = await existingHandle.getFile(); Shared Worker
// Create Shared Worker
let worker = new SharedWorker("SharedWorker.js");
// Listen for messages from Shared Worker
worker.port.addEventListener("message", function(event) {
console.log("Message received from worker: " + event);
});
// Send messages to Shared Worker
worker.port.postMessage("Send message to worker"); findLast() and findLastIndex()
const list = ["shirt","pants","shoes","hat","shoestring","dress"];
const hasShoeString = (string) => string.includes("shoe");
console.log(list.findLast(hasAppString));
// shoestring
console.log(list.findLastIndex(hasAppString));
// 4 at()
const list = ["shirt","pants","shoes","hat","shoestring","dress"];
// Instead of this:
console.log(list[list.length - 2]);
// It's as easy as:
console.log(list.at(-2)); strict-dynamic source expression
// strict-dynamic source expression
// Without strict-dynamic
Content-Security-Policy: script-src desired-script.com dependent-script-1.com
dependent-script-2.com dependent-script-3.com; default-src "self";
// With strict-dynamic
Content-Security-Policy: default-src "self"; script-src "nonce-desired" "strict-dynamic"; Resources
Related sessions
-
28 min -
16 min -
35 min -
22 min -
9 min -
15 min -
23 min -
18 min