Никита Дубко, Яндекс.Поиск
Никита Дубко, разработчик интерфейсов
<IMG SRC="triangle.xbm" ALIGN="TOP" ALT="Warning">
<a href="http://machine/htbin/imagemap/sample">
<IMG SRC="sample.xbm" ISMAP>
</a>
<img>
<img src="cats.png">
GET /img/cats.png HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) ...
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://127.0.0.1:8080/img/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
GET /img/cats.png HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) ...
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://127.0.0.1:8080/img/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
const img = new Image();
img.onload = function() { /* ... */ };
img.onerror = function() { /* ... */ };
img.src = 'path/to/image.png';
src=""
;src="path/to/current-page"
;<img src="cats.png"
alt="Три котёнка.">
img {
font-family: 'Helvetica';
color: darkred;
text-align: center;
min-height: 3em;
display: block;
position: relative;
}
img::before {
content: "Картинка поломалась :(";
display: block;
}
img::after {
content: "(url: " attr(src) ")";
display: block;
}
Styling Broken Images
img::after {
content: "📷" " " attr(alt);
z-index: 1;
line-height: 3em;
color: rgb(100, 100, 100);
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #ccc;
}
Styling Broken Images
self.addEventListener('fetch', (e) => {
e.respondWith(
fetch(e.request)
.then((response) => {
if (response.ok) return response;
// 🤔
})
.catch((err) => {
// ❌
})
)
});
Handling broken
images with the service worker
function isImage(fetchRequest) {
return fetchRequest.method === "GET"
&& fetchRequest.destination === "image";
}
self.addEventListener('install', (e) => {
self.skipWaiting();
e.waitUntil(
caches.open("precache").then((cache) => {
cache.add("/broken.png");
})
);
});
Handling broken
images with the service worker
self.addEventListener('fetch', (e) => {
e.respondWith(
fetch(e.request)
.then((response) => {
if (response.ok) return response;
if (isImage(e.request)) {
return caches.match("/broken.png");
}
})
.catch((err) => {
if (isImage(e.request)) {
return caches.match("/broken.png");
}
})
)
});
Handling broken
images with the service worker
<img src="cats.png"
alt="Три котёнка.">
alt=""
alt=""
Декоративная картинка без смысловой нагрузки — alt
остаётся пустым.
Картинка со смыслом, которая дополняет или иллюстрирует текст. В alt
пишется уникальное
и ёмкое
описание изображения. Избегайте повторения предложений, которые уже есть на странице.
В конце описания в alt
всегда ставится точка, чтобы помочь скринридеру
сделать
паузу перед следующим дальше контентом.
CONTRIBUTING.md
сайта Веб-стандартов
background-image
alt=""
role="presentation"
aria-hidden="true"
<figure>
<img src="picture.png" alt="Ёмкое описание картинки.">
<figcaption>
Информация об изображении
(например, фото: автор).
</figcaption>
</figure>
<svg role="img"
aria-label="Описание графика"
aria-described-by="chart-desc">
<desc id="chart-desc">
Подробное описание графика
</desc>
</svg>
<img src="cats.png"
title="Три котёнка.">
<img src="cats.png"
alt="Три котёнка."
width="500">
od -t u1 picture.png | less
0000000 137 80 78 71 13 10 26 10 0 0 0 13 73 72 68 82 0000020 0 0 7 128 0 0 4 56 8 6 0 0 0 232 211 193 0000040 67 0 0 32 0 73 68 65 84 120 94 236 157 7 152 93 0000060 87 117 239 255 167 221 50 125 70 163 222 172 46 217 146 229 0000100 110 185 96 27 66 47 110 88 174 184 0 14 193 1 66 224 0000120 81 220 0 83 19 32 47 1 30 37 148 132 22 30 132 16 0000140 120 4 135 98 192 198 113 145 109 220 173 94 71 178 186 52 0000160 189 220 118 206 217 239 91 187 220 123 230 206 157 185 119 154
od -t u2 picture.gif | less
0000000 18759 14406 24889 500 275 247 60672 44217 0000020 16992 28728 27499 54514 18639 18754 38345 43654 0000040 26485 29547 9371 5653 42714 36246 46224 25751 0000060 51543 30857 6178 21282 21574 12611 60471 39341 0000100 33962 55926 34454 35472 13456 8994 39309 42698 0000120 54955 29592 39528 25193 34169 47286 27000 42394 0000140 35030 21592 45223 42984 22889 2065 22032 26190 0000160 31400 47730 34708 13928 47153 34188 17492 65351
<img src="cats.png"
alt="Три котёнка."
width="500" height="275">
<img src="cats.png"
alt="Три котёнка."
intrinsicsize="400x300">
intrinsicsize Attribute on Media
Elements Explainer
<style>
img {
width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height);
}
</style>
<img src="image.jpg" width="500" height="500">
Setting Height
And Width On Images Is Important Again
<img src="cats.png"
alt="Три котёнка."
width="500" height="275"
referrerpolicy="origin">
Referer and Referrer-Policy best
practices
res.cloudinary.com
:method: GET
:path: /.../picture.png
:scheme: https
accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
cache-control: no-cache
pragma: no-cache
referer: http://example.com/
sec-fetch-dest: image
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) ...
<img src="cats.png"
alt="Три котёнка."
width="500" height="275"
crossorigin>
HTML Living
Standard
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');
ctx.drawImage(image, 0, 0);
// DOMException: Failed to execute 'getImageData'
// on 'CanvasRenderingContext2D':
// The canvas has been tainted by cross-origin data.
<img src="cats.png"
alt="Три котёнка."
width="500" height="275"
load="lazy">
Browser-level image lazy-loading
for the web
auto
— решает браузер;eager
— загружать сразу;lazy
— не загружать, пока от вьюпорта до картинки не будет определённого
расстояния.Connection | px |
---|---|
Unknown | 3000 |
Offline | 8000 |
Slow 2G | 8000 |
2G | 6000 |
3G | 2500 |
4G | 1250 |
<img src="hero.jpg" alt="…">
<img data-src="unicorn.jpg" alt="…" loading="lazy" class="lazyload">
<script>
if ('loading' in HTMLImageElement.prototype) {
const images = document.querySelectorAll('img[loading="lazy"]');
images.forEach(img => {
img.src = img.dataset.src;
});
} else {
const script = document.createElement('script');
script.src =
'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.1.2/lazysizes.min.js';
document.body.appendChild(script);
}
</script>
Browser-level image lazy-loading
for the web
loading=auto
<img src="cats.png"
alt="Три котёнка."
width="500" height="275"
decoding="async">
"decode" attribute on
<img>
sync
— синхронно с контентом;async
— асинхронно, после контента;auto
— решает браузер.<img srcset="cats-320.jpg 320w,
cats-480.jpg 480w,
cats-800.jpg 800w"
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
100vw"
src="cats-800.jpg">
srcset
— задаем физический размер изображений;sizes
— вычисляем размер области под картинку;srcset
самое подходящее по размеру изображение.<img src="picture.png"
srcset="picture@2x.png 2x">
<meta name="viewport"
content="width=device-width">
npx imagingheap https://gdg-russia.com/
<img src="https://static.tildacdn.com/.../logo-rus-bel.png"> ╔══════════╤══════════╤════════╤════════════╤════════╤════════════╤════════╤════════════╗ ║ │ Image │ @1x │ @1x │ @2x │ @2x │ @3x │ @3x ║ ║ │ Width in │ Image │ Percentage │ Image │ Percentage │ Image │ Percentage ║ ║ Viewport │ Layout │ Width │ Match │ Width │ Match │ Width │ Match ║ ╟──────────┼──────────┼────────┼────────────┼────────┼────────────┼────────┼────────────╢ ║ 320px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 400px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 480px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 560px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 640px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 720px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 800px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 880px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 960px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 1040px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 1120px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 1200px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ║ 1280px │ 220px │ 1706px │ 775.5% │ 1706px │ 387.7% │ 1706px │ 258.5% ║ ╚══════════╧══════════╧════════╧════════════╧════════╧════════════╧════════╧════════════╝ Legend: @1x <100% >150% Above @1x <75% 75%–92%
<picture>
<source type="image/avif"
srcset="cats.avif">
<source type="image/webp"
srcset="cats.webp">
<img src="cats.jpg"
width="20" height="20">
</picture>
async function supportsImgType(type) {
let img = document.createElement('img');
document.createElement('picture').append(
Object.assign(document.createElement('source'), {
srcset: 'data:,x', // валидный урл, который не дёргает сеть
type
}),
img
);
await 0; // даём примениться currentSrc
return !!img.currentSrc; // если браузер умеет, заполнит значение currentSrc
}
for (let type of ['image/png', 'image/jpeg', 'image/webp', 'image/avif']) {
supportsImgType(type).then(supported => console.log(`${type}: ${supported}`));
}
RReverser/detect-img-type-support.js
<picture>
<source type="image/avif"
srcset="cats@1x.avif 1x, cats@2x.avif 2x">
<source type="image/webp"
srcset="cats@1x.webp 1x, cats@2x.webp 2x">
<img src="cats@1x.jpg"
srcset="cats@2x.jpg 2x"
width="20" height="20">
</picture>
<picture>
<source type="image/avif"
media="(min-width: 1150px)"
srcset="cats-desktop@1x.avif 1x, cats-desktop@2x.avif 2x">
<source type="image/avif"
srcset="cats-mobile@1x.avif 1x, cats-mobile@2x.avif 2x">
<source type="image/webp"
media="(min-width: 1150px)"
srcset="cats-desktop@1x.webp 1x, cats-desktop@2x.webp 2x">
<source type="image/webp"
srcset="cats-mobile@1x.webp 1x, cats-mobile@2x.webp 2x">
<source media="(min-width: 1150px)"
srcset="cats-desktop@1x.jpg 1x, cats-desktop@2x.jpg 2x">
<img src="cats-mobile@1x.jpg"
srcset="cats-mobile@2x.jpg 2x"
width="20" height="20">
</picture>
gulp.task('convert', gulp.series(
'clean',
'convert:retina',
'convert:webp',
'convert:avif',
gulp.parallel(
'optimize:guetzli',
'optimize:png',
'optimize:svg'
),
));
const gulp = require('gulp');
const exec = require('gulp-exec');
const config = require('./config');
gulp.task('convert:avif', () => {
let src = `${config.base.dist}/**/*.{jpg,png}`;
return gulp.src(src)
.pipe(exec((file) =>
`avifenc -c aom --min 20 --max 50 \
${file.path} \
${file.path.replace(/(png|jpg)$/ig, 'avif')}`
));
});
brew install joedrago/repo/avifenc
https://imgproxy.evilmartians.com/PzslbQaEHzr9AEfdvP0UCF49tg0S1PoQiGsHrNyf11s/rs:fill:960:540/dpr:2/g:ce/wm:0.5:soea:0:0:0.2/wmu:aHR0cHM6Ly9pbWdwcm94eS5uZXQvd2F0ZXJtYXJrLnN2Zw/plain/https:%2F%2Fwww.nasa.gov%2Fsites%2Fdefault%2Ffiles%2Fthumbnails%2Fimage%2Fpia22228.jpg
imgproxy<picture>
<source srcset="picture-dark.png"
media="(prefers-color-scheme: dark)">
<img src="picture-light.png">
</picture>
<picture>
<source srcset="no-motion.jpg"
media="(prefers-reduced-motion: reduce)">
<img srcset="animated.gif">
</picture>
Reducing
motion with the picture element
<p>
Please select a shape:
<img src="shapes.png" usemap="#shapes">
<map name="shapes">
<area shape=rect coords="50,50,100,100"> <!-- the hole in the red box -->
<area shape=rect coords="25,25,125,125" href="red.html" alt="Red box.">
<area shape=circle coords="200,75,50" href="green.html" alt="Green circle.">
<area shape=poly coords="325,25,262,125,388,125" href="blue.html" alt="Blue triangle.">
<area shape=poly coords="450,25,435,60,400,75,435,90,450,125,465,90,500,75,465,60"
href="yellow.html" alt="Yellow star.">
</map>
</p>
Image maps
<a href="/imagemapper">
<img src="image.png" ismap />
</a>
/imagemapper?3,9
,
где
3 — смещение по горизонтали,
9 — смещение по
вертикали
img {
max-width: 100%;
height: auto;
}
.img-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
height: 0;
overflow: hidden;
}
.img-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Setting Height
And Width On Images Is Important Again
.img-container img {
object-fit: cover;
}
<div class="img-container">
<img src="picture.png"
alt=""
class="img-blur"
aria-hidden="true">
<img src="picture.png" alt="Picture">
</div>
.img-container > * {
position: absolute;
top: var(--offset, 0);
left: var(--offset, 0);
width: calc(100% - 2 * var(--offset, 0px));
height: calc(100% - 2 * var(--offset, 0px));
object-fit: contain;
}
.img-blur {
--blur: 20px;
--offset: calc(-1 * var(--blur));
object-fit: cover;
filter: blur(var(--blur));
}
.pixelated {
width: 512px;
height: 512px;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
<link rel="preload"
as="image"
href="important-image.jpg">
Preload late-discovered Hero images
faster