A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 43KB

4 vuotta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Lazy Loading Images on the Web (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://developer.telerik.com/featured/lazy-loading-images-on-the-web/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Lazy Loading Images on the Web (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://developer.telerik.com/featured/lazy-loading-images-on-the-web/">Source originale du contenu</a></h3>
  445. <p>Images on the web offer a bit of a conundrum. They are often what makes a web page feel vibrant and interesting, but they can also dramatically hurt web page performance by adding a significant amount of weight to a page.</p>
  446. <p>On this site, we use a lot of images within our content. As the person who maintains this site, I do my best to optimize them, but in many cases the sum total of the image weight on a page is still significant (especially as we’ve come to rely on animated GIFs for illustrations). The thing is, much of this image weight sits well offscreen. As much as I’d like to believe that every visitor reads every word we write, it’s highly likely that many visitors are downloading images that they simply do not ever see.</p>
  447. <h2>The Page Weight Problem</h2>
  448. <p>This issue isn’t unique to this site. <a href="http://mobile.httparchive.org/interesting.php?a=All&amp;l=Jul%201%202015">According to HTTPArchive</a>, images now account for 63% of page weight. As developers, we work hard to optimize and minify our JavaScript code and CSS to make them as small as possible, but, by sheer volume, that can never have the impact of simply reducing image weight.</p>
  449. <p>Consider that, as <a href="http://www.soasta.com/blog/page-bloat-average-web-page-2-mb/">Tammy Everts notes</a>, “images alone comprise more page weight (1310 KB) than an entire page in May 2012 (1059 KB), just three years ago.” It’s a problem for any visitor with a data cap – which is pretty much everyone, especially internationally. Images your visitors never see are costing them money.</p>
  450. <p>And the page weight issue is only getting worse:</p>
  451. <blockquote><p>As of May [2015], the average web page surpassed the 2 MB mark. This is almost twice the size of the average page just three years ago. – <a href="http://www.soasta.com/blog/page-bloat-average-web-page-2-mb/">source</a></p>
  452. </blockquote>
  453. <p>So, what can we do?</p>
  454. <p>Well, one option is to “lazy load” images. In essence, we use a little bit of JavaScript to determine which images are in (or near) the viewport and only download images that the user will likely see. It is a strategy that is not without its own flaws (some of which are covered in this <a href="http://www.smashingmagazine.com/2015/02/redefining-lazy-loading-with-lazy-load-xt/">article</a> by the author of one lazy loading library). However, if your site relies on image-heavy content, it is a strategy worth considering – especially when targeting mobile devices.</p>
  455. <p>In the remainder of this article, we’ll look at a number of different solutions for implementing lazy loading of images.</p>
  456. <h3>Sample Page</h3>
  457. <p>To assist us in reviewing the options for lazy loading images, I’ve built a simple example web page. The page is designed as a “Teen Titans Go” fan page that lists out a large number of the characters, with their images.</p>
  458. <p><a href="http://developer.telerik.com/wp-content/uploads/2015/10/samplepage.jpg"><img src="http://developer.telerik.com/wp-content/uploads/2015/10/samplepage.jpg" alt="Teen Titans" width="800" height="523" class="alignnone size-full wp-image-60881"/></a></p>
  459. <p>I rebuilt the page using a variety of solutions to compare how they are implemented and how they work. The full code for all the variations can be found <a href="https://github.com/remotesynth/Lazy-Loading-Images">on GitHub</a>.</p>
  460. <h2>Custom Solution</h2>
  461. <p>At its most basic implementation, building a custom solution for lazy loading images is not complicated. Here’s what we need to do:</p>
  462. <ol>
  463. <li>Build the HTML so that images are not automatically loaded (this is typically done by specifying the actual <code>src</code> in a <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_data_attributes">data attribute</a>);</li>
  464. <li>Watch changes to the viewport or scrolling to see which images have or may soon enter the viewport;</li>
  465. <li>Swap the data attribute and the <code>src</code> so that the image is loaded.</li>
  466. </ol>
  467. <p>For the first item, you might be concerned that an <code>img</code> tag without a <code>src</code> is not valid HTML. Unfortunately, it appears that you cannot place the actual source in the <code>src</code> attribute while somehow preventing the browser from loading the image using JavaScript. You can place a “dummy” source image, however, such as a spacing or loading gif.</p>
  468. <p>Let’s start by seeing how to build this in plain JavaScript.</p>
  469. <h3>Plain JavaScript</h3>
  470. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/custom.html">View this demo</a></p>
  471. <p>The first thing is to make sure the images do not load by specifying the image source using a <code>data-src</code> attribute rather than placing it in the <code>src</code>. In our simple example, we are not going to worry about setting a dummy <code>src</code> or loading image.</p>
  472. <pre><code class="language-markup">&lt;img data-src="images/Robin.jpg" alt="" /&gt;</code></pre>
  473. <p>I should note that you may want to add a width and height to your images to ensure that they take up the appropriate space. For our sample that isn’t completely necessary.</p>
  474. <p>Now we need a way to test if an image is within the viewport. Thankfully, with support for <code>getBoundingClientRect</code> being <a href="http://caniuse.com/#feat=getboundingclientrect">nearly universal</a>, there is a reliable method to do that. The function we are using is borrowed from a <a href="http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/7557433#7557433">response on StackOverflow</a>.</p>
  475. <pre><code class="language-javascript">function isElementInViewport (el) {&#13;
  476. &#13;
  477. var rect = el.getBoundingClientRect();&#13;
  478. &#13;
  479. return (&#13;
  480. rect.top &gt;= 0 &amp;&amp;&#13;
  481. rect.left &gt;= 0 &amp;&amp;&#13;
  482. rect.bottom &lt;= (window.innerHeight || document.documentElement.clientHeight) &amp;&amp; /*or $(window).height() */&#13;
  483. rect.right &lt;= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */&#13;
  484. );&#13;
  485. }</code></pre>
  486. <p>To use this function, we’d pass an image or an image container and the function will return true if the passed element is within the viewport, or false if it is not.</p>
  487. <p>Next, we need to get all of the images we want to lazy load and then test them against this function every time a scroll or change to the viewport occurs. Let’s look at the full code and then examine what it is doing.</p>
  488. <pre><code class="language-javascript">//these handlers will be removed once the images have loaded&#13;
  489. window.addEventListener("DOMContentLoaded", lazyLoadImages);&#13;
  490. window.addEventListener("load", lazyLoadImages);&#13;
  491. window.addEventListener("resize", lazyLoadImages);&#13;
  492. window.addEventListener("scroll", lazyLoadImages);&#13;
  493. &#13;
  494. function lazyLoadImages() {&#13;
  495. var images = document.querySelectorAll("#main-wrapper img[data-src]"),&#13;
  496. item;&#13;
  497. // load images that have entered the viewport&#13;
  498. [].forEach.call(images, function (item) {&#13;
  499. if (isElementInViewport(item)) {&#13;
  500. item.setAttribute("src",item.getAttribute("data-src"));&#13;
  501. item.removeAttribute("data-src")&#13;
  502. }&#13;
  503. })&#13;
  504. // if all the images are loaded, stop calling the handler&#13;
  505. if (images.length == 0) {&#13;
  506. window.removeEventListener("DOMContentLoaded", lazyLoadImages);&#13;
  507. window.removeEventListener("load", lazyLoadImages);&#13;
  508. window.removeEventListener("resize", lazyLoadImages);&#13;
  509. window.removeEventListener("scroll", lazyLoadImages);&#13;
  510. }&#13;
  511. }</code></pre>
  512. <p>The first thing we do is ensure that we are watching for scrolls and changes to the viewport by listening on the <code>DOMContentLoaded</code>, <code>load</code>, <code>resize</code> and <code>scroll</code> events. Every time one of these events occurs, we call a method to check if any images have entered the viewport. (If you’re concerned about how often these events will be called, I discuss that issue in a later section.)</p>
  513. <p>Looking at the <code>lazyLoadImages</code> method, we first get all the images that have not yet loaded. We do this by selecting only those that still have a <code>data-src</code> attribute. (As we’ll see in upcoming examples, there are a number of methods to do this, but, honestly, I have not tested which method is more performant. However, the performance implication of DOM node retrieval is negligible for the overwhelming majority of cases)</p>
  514. <p>If the image has entered the viewport, we swap the value of the <code>data-src</code> attribute with the <code>src</code> attribute and remove the <code>data-src</code> attribute. Finally, if there are no images left unloaded, we simply remove the event listeners.</p>
  515. <p>All in all, I’d say this was pretty simple, though it’s a very limited implementation. You could expand upon this to test for images sitting just outside the viewport so that there would be a potentially smaller visibility delay for the user – although this only really helps if the user manually scrolls.</p>
  516. <h3>jQuery</h3>
  517. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/custom-jquery.html">View this demo</a></p>
  518. <p>If you use jQuery on your site, you can save a few lines of code. For instance, we can move turning the event handlers on and off into a single line of code. However, all in all, we save only a handful lines of code.</p>
  519. <pre><code class="language-javascript">$(window).on('DOMContentLoaded load resize scroll', function () {;&#13;
  520. var images = $("#main-wrapper img[data-src]");&#13;
  521. // load images that have entered the viewport&#13;
  522. $(images).each(function (index) {&#13;
  523. if (isElementInViewport(this)) {&#13;
  524. $(this).attr("src",$(this).attr("data-src"));&#13;
  525. $(this).removeAttr("data-src");&#13;
  526. }&#13;
  527. })&#13;
  528. // if all the images are loaded, stop calling the handler&#13;
  529. if (images.length == 0) {&#13;
  530. $(window).off('DOMContentLoaded load resize scroll')&#13;
  531. }&#13;
  532. })&#13;
  533. &#13;
  534. // source: http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/7557433#7557433&#13;
  535. function isElementInViewport (el) {&#13;
  536. var rect = el.getBoundingClientRect();&#13;
  537. &#13;
  538. return (&#13;
  539. rect.top &gt;= 0 &amp;&amp;&#13;
  540. rect.left &gt;= 0 &amp;&amp;&#13;
  541. rect.bottom &lt;= $(window).height() &amp;&amp;&#13;
  542. rect.right &lt;= $(window).width()&#13;
  543. );&#13;
  544. }</code></pre>
  545. <p>If you go the build your own solution route, choosing straight JavaScript versus jQuery ends up being just a matter of personal preference.</p>
  546. <h3>Problems with These Solutions</h3>
  547. <p>Right now, we’ve just implemented extremely basic lazy loading functionality for images. A more robust solution would handle setting an offset, whereby elements just off screen are loaded as well as those on screen. Also, while the performance of our script may be suitable for a page with a relatively limited number of images (and, in this case, the images are not huge), its performance would likely degrade significantly for a more complex page with more images as the event handler is constantly being called and looping through a list of images.</p>
  548. <p>Some additional features might be nice too, for instance having success and failure handlers for the loading process could prove useful. It’d also be nice to implement things like <code>srcset</code> and <code>picture</code> for responsive images. Lastly, I might want to add support for a loading images in case the image file isn’t fully downloaded when it enters the viewport. I might even, perhaps, want some sort of animation or transition when images appear.</p>
  549. <p>The good news is that there are a lot of pre-built libraries for handling lazy loading of images available that do many of these things. Let’s look at some of them.</p>
  550. <h2>Libraries</h2>
  551. <p>For the most part, as you look through the library variations below, you may notice that they all function very similarly. For our sample, the implementation looks surprisingly similar across the board. Choosing one comes down to whether you want to require jQuery (most do) and what kind of options you need, as some offer far more configuration than others.</p>
  552. <h3>LazyLoad</h3>
  553. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/lazyload.html">View this demo</a></p>
  554. <p>One of the first lazy loading image libraries was the <a href="http://www.appelsiini.net/projects/lazyload">Lazy Load Plugin for jQuery</a>. It inspired a number of additional libraries including <a href="http://verlok.github.io/lazyload/">LazyLoad</a>.</p>
  555. <p>One difference between LazyLoad and the other solutions is that the images use <code>data-original</code> for the source rather than <code>data-src</code>.</p>
  556. <pre><code class="language-markup">&lt;img data-original="images/GreenLantern.jpg" alt="" width="374" height="260" /&gt;</code></pre>
  557. <p>Once our images are set up, lazy loading them is incredibly simple. Just include the JavaScript file (obviously) and initialize LazyLoad. In our sample code below, we’ve added a threshold of 50 pixels, meaning that items just off-screen will also be loaded. We’ve also added a success handler.</p>
  558. <pre><code class="language-javascript">var myLazyLoad = new LazyLoad({&#13;
  559. threshold: 50,&#13;
  560. callback_load: function(e) {&#13;
  561. console.log($(e).attr("data-original") + " loaded" );&#13;
  562. }&#13;
  563. });</code></pre>
  564. <p>The image below shows scrolling through the mobile version of the page loading one image at a time. In the console, each image indicates that it has successfully loaded.</p>
  565. <p><a href="http://developer.telerik.com/wp-content/uploads/2015/10/LazyLoad_opt.gif"><img src="http://developer.telerik.com/wp-content/uploads/2015/10/LazyLoad_opt.gif" alt="LazyLoad_opt" width="400" height="533" class="alignnone size-full wp-image-60891"/></a></p>
  566. <p>LazyLoad supports a number of <a href="http://verlok.github.io/lazyload/#options">additional options</a>, which we won’t cover here.</p>
  567. <h3>bLazy.js</h3>
  568. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/blazy.html">View this demo</a></p>
  569. <p><a href="http://dinbror.dk/blazy/">bLazy</a> (or [Be]Lazy) is a relatively recent library that aims to offer a number of key features while remaining small and lightweight. One significant difference of bLazy is that it does not require jQuery.</p>
  570. <p>To identify which images are to be lazy loaded, by default bLazy requires you to add a CSS class onto each. In this example, I am using the default CSS class, but the <a href="http://dinbror.dk/blog/blazy/#Offset">selector can be customized</a>.</p>
  571. <pre><code class="language-markup">&lt;img data-src="images/Batgirl.jpg" alt="" width="374" height="260" class="b-lazy" /&gt;</code></pre>
  572. <p>After that, you simply include and initialize the script. Below I have done that while also setting an offset and a success and error handler.</p>
  573. <pre><code class="language-javascript">var bLazy = new Blazy({&#13;
  574. offset: 50,&#13;
  575. success: function(e){&#13;
  576. console.log($(e).attr("src") + " loaded" );&#13;
  577. },&#13;
  578. error: function(ele, msg){&#13;
  579. console.log(msg)&#13;
  580. }&#13;
  581. });</code></pre>
  582. <p>Below you can see multiple images loading at a time as the page is scrolled.</p>
  583. <p><a href="http://developer.telerik.com/wp-content/uploads/2015/10/blazy_opt.gif"><img src="http://developer.telerik.com/wp-content/uploads/2015/10/blazy_opt.gif" alt="blazy_opt" width="600" height="401" class="alignnone size-full wp-image-60901"/></a></p>
  584. <p>bLazy supports a number of <a href="http://dinbror.dk/blog/blazy/#Options">options</a> including the ability to serve smaller or larger images based upon screen size. Check the <a href="http://dinbror.dk/blog/blazy/">documentation</a> for more details.</p>
  585. <h3>Unveil</h3>
  586. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/unveil.html">View this demo</a></p>
  587. <p><a href="http://luis-almeida.github.io/unveil/">Unveil</a> is another script inspired by <a href="http://www.appelsiini.net/projects/lazyload">jQuery_lazyload</a>, so, it does require jQuery. It is very lightweight, however, being less than 1k.</p>
  588. <p>One nice thing about Unveil is that it does not require any special markup on your images beyond the <code>data-src</code> attribute.</p>
  589. <pre><code class="language-markup">&lt;img data-src="images/WonderTwins.jpg" alt="" /&gt;</code></pre>
  590. <p>This is because we specify the images that will be supplied to Unveil via a jQuery selector. Simply load the script and apply Unveil to our selected images on document ready.</p>
  591. <pre><code class="language-javascript">$(function () {&#13;
  592. $("#main-wrapper img").unveil(50, function() {&#13;
  593. $(this).load(function() {&#13;
  594. console.log(this.src + " loaded");&#13;
  595. });&#13;
  596. });&#13;
  597. })</code></pre>
  598. <p>As with the prior examples, we’ve also initialized Unveil with an offset of 50 pixels and a success handler.</p>
  599. <p>Unveil is more lightweight than the other libraries and, because of this, doesn’t offer quite as many options as the others discussed. Check <a href="http://luis-almeida.github.io/unveil/">the documentation</a> for additional usage options.</p>
  600. <h3>Lazy Load XT</h3>
  601. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/lazyloadxt.html">View this demo</a></p>
  602. <p><a href="https://github.com/ressio/lazy-load-xt">Lazy Load XT</a> probably offers the most features of any of the libraries discussed here. In their <a href="http://www.smashingmagazine.com/2015/02/redefining-lazy-loading-with-lazy-load-xt/">Smashing Magazine article</a>, the authors described their aim to fix many of the deficiencies in the other available libraries.</p>
  603. <p>Lazy Load XT splits itself into two main libraries – the first has the core features and the second adds some of the more advanced options. It also offers additional scripts as plugins and CSS files for effects. For our sample, we only need the basic script file.</p>
  604. <pre><code class="language-markup">&lt;script src="js/jquery.lazyloadxt.js"&gt;&lt;/script&gt;</code></pre>
  605. <p>Just as with Unveil previously, our images only need a <code>data-src</code> attribute. There is no need for any special classes or additional data attributes.</p>
  606. <pre><code class="language-markup">&lt;img data-src="images/BeastBoy.jpg" alt="" /&gt;</code></pre>
  607. <p>Now, all we need to do is initialize Lazy Load XT. In the below code, we are also adding success and error handlers by listening for specific events that LazyLoad emits.</p>
  608. <pre><code class="language-javascript">$(window).lazyLoadXT();&#13;
  609. $(window).on("lazyload", function(e){&#13;
  610. console.log(e.target.src + " loaded");&#13;
  611. });&#13;
  612. $(window).on("lazyerror", function(e){&#13;
  613. console.log("Error loading " + e.target.src);&#13;
  614. });</code></pre>
  615. <p>Lazy Load XT supports a lot of <a href="https://github.com/ressio/lazy-load-xt#options">options</a> and <a href="https://github.com/ressio/lazy-load-xt#events">events</a>. I should note, however, that I had some difficulty getting certain options to work easily. While Lazy Load XT has a lot of examples and documentation, it seems (to me anyway) to be missing the simple examples, such as using some of the basic events and configuration options. I also, unfortunately, had some difficulty even getting the basic effects to work (in theory, this was simply a matter of including the correct CSS).</p>
  616. <p>On a more positive note, Lazy Load XT has extensive support for responsive images. It even offers support for lazy loading videos and iframes, making it by far the most comprehensive lazy loading library I came across. Someone has even created a <a href="https://wordpress.org/plugins/lazy-load-xt/">WordPress plugin</a> to lazy load images included within WordPress posts.</p>
  617. <h2>Telerik Platform – Responsive Images Service</h2>
  618. <p><a href="https://rawgit.com/remotesynth/Lazy-Loading-Images/master/telerik.html">View this demo</a></p>
  619. <p>Let’s look at one more example. Within the <a href="http://www.telerik.com/platform">Telerik Platform</a>‘s Backend Services offering is a <a href="http://docs.telerik.com/platform/backend-services/javascript/responsive-images/introduction">responsive images service</a>. It offers:</p>
  620. <ul>
  621. <li>The ability to specify image dimensions for individual images;</li>
  622. <li>Automatic resizing of images based on the target container dimensions.</li>
  623. </ul>
  624. <p>The main benefit for our purposes is that using this service the images are automatically responsive. For example, we can place a huge image in Backend Services but the service will automatically serve the appropriate image based on the device’s screen size and pixel density — no need to use srcset/picture, or to upload ten versions of the images.</p>
  625. <p>For a full overview of the responsive images offering, check out <a href="http://developer.telerik.com/featured/responsive-images-theres-service/">this article by Hristo Borisov</a>.</p>
  626. <p>In addition, Backend Services offer the ability to store and serve files from the CDN. While this isn’t necessary to utilize the responsive images features, it is definitely a plus.</p>
  627. <p><a href="http://developer.telerik.com/wp-content/uploads/2015/10/Platform_files.jpg"><img src="http://developer.telerik.com/wp-content/uploads/2015/10/Platform_files.jpg" alt="Platform_files" width="800" height="467" class="alignnone size-full wp-image-60911"/></a></p>
  628. <p>After uploading the files, we need to right click the file name (i.e. the Batgirl.jpg link in the screenshot above) to get its URL (see <a href="http://docs.telerik.com/platform/backend-services/javascript/files/files-getting-started#obtain-the-file-download-url">the documentation</a> if you need additional details).</p>
  629. <p>In the HTML, we need to swap all the images for the CDN-hosted versions within the <code>data-src</code> attribute. We also need to add a <code>data-responsive</code> attribute to each image. This enables the automatic image replacement – more on that in a moment. For instance, here’s the image tag for Raven (aka my favorite Teen Titan), where the URL follows an <code>/api-key/image-id</code> format.</p>
  630. <pre><code class="language-markup">&lt;img data-src="https://bs2.cdn.telerik.com/v1/h5tc7Cws1Qi9oluI/de916422-6e1f-11e5-a7c8-356526b6da24" alt="" data-responsive /&gt;</code></pre>
  631. <p>We are going to use the JavaScript SDK that Backend Services provides to <a href="http://docs.telerik.com/platform/backend-services/javascript/responsive-images/responsive-images-automatic-use">automatically apply responsive images</a>. So first, let’s include the script.</p>
  632. <pre><code class="language-markup">&lt;script src="https://bs-static.cdn.telerik.com/1.5.6/everlive.all.min.js"&gt;&lt;/script&gt;</code></pre>
  633. <p>Next we need to initialize the service. There are a number of <a href="http://docs.telerik.com/platform/backend-services/javascript/responsive-images/responsive-images-configure-attributes#configuring-html-attributes">additional options</a> that you can specify here, but let’s stick with the defaults.</p>
  634. <pre><code class="language-javascript">var el = new Everlive({&#13;
  635. apiKey: "your-api-key",&#13;
  636. scheme: "https"&#13;
  637. });</code></pre>
  638. <p>Since the service is not dependent on jQuery, let’s go ahead and leverage the plain JavaScript version we built earlier. We also need to include the <code>isElementInViewport</code> function shown earlier. After that, it’s just some minor changes to the plain JavaScript code we wrote previously.</p>
  639. <pre><code class="language-javascript">window.addEventListener("DOMContentLoaded", lazyLoadImages);&#13;
  640. window.addEventListener("load", lazyLoadImages);&#13;
  641. window.addEventListener("resize", lazyLoadImages);&#13;
  642. window.addEventListener("scroll", lazyLoadImages);&#13;
  643. &#13;
  644. function lazyLoadImages() {&#13;
  645. // select only images that do not yet have a src attribute added&#13;
  646. var images = document.querySelectorAll("#main-wrapper img:not([src])"),&#13;
  647. item;&#13;
  648. &#13;
  649. // load images that have entered the viewport&#13;
  650. [].forEach.call(images, function (item) {&#13;
  651. if (isElementInViewport(item)) {&#13;
  652. el.helpers.html.process(item, {}, successHandler, errorHandler);&#13;
  653. }&#13;
  654. })&#13;
  655. // if all the images are loaded, stop calling the handler&#13;
  656. if (images.length == 0) {&#13;
  657. window.removeEventListener("DOMContentLoaded", lazyLoadImages);&#13;
  658. window.removeEventListener("load", lazyLoadImages);&#13;
  659. window.removeEventListener("resize", lazyLoadImages);&#13;
  660. window.removeEventListener("scroll", lazyLoadImages);&#13;
  661. }&#13;
  662. }&#13;
  663. &#13;
  664. function successHandler (e) {&#13;
  665. console.log("success");&#13;
  666. }&#13;
  667. function errorHandler (e) {&#13;
  668. console.log(e);&#13;
  669. }</code></pre>
  670. <p>There are a couple of small but important changes to note here. First, we have changed our selector to select any images that do not have a <code>src</code> attribute. The automatic responsive image loading does not like us removing the <code>data-src</code> attribute, but, using this method, we can still select only those images that have not yet been loaded.</p>
  671. <p>Second, if the image is within the viewport, we call <code>el.helpers.html.process()</code> to have the service automatically process them. We’re not passing any options at this time, although we did set success and failure handlers. Since we are loading from an external CDN, it would probably be worth expanding this to support an offset in the future.</p>
  672. <p>There’s many more options available in the responsive images service. If you’re interested in utilizing it, I suggest <a href="http://docs.telerik.com/platform/backend-services/javascript/responsive-images/introduction">reading through the documentation</a> to learn more about what is available.</p>
  673. <h2>Go Further</h2>
  674. <p>Hopefully, by now, you have a good overview of the benefits of lazy loading images and some of the techniques available to you for achieving it. Our samples were intentionally simple, but the options exist to take this to the next level including handling things like responsive images, video, iframes and widgets, depending on the needs of your site.</p>
  675. <p>The important thing to keep in mind here is the cost/benefit to your users. Does the potential for a slight delay in image loading (and, perhaps, a decrease in “scan-ability” of the content) offset the problem of image bloat? Or does your content rely heavily on large imagery that the user may never see, but which may be costing them in terms of loading time or data overages? Is it possibly worth implementing this strategy but targeting specifically mobile, where data can be slow and costly, while ignoring desktops, where WiFi would pretty much be guaranteed?</p>
  676. <p>Share your thoughts in the comments.</p>
  677. <p><em>Header image courtesy of <a href="https://flic.kr/p/7vuxbx">Andy Rennie</a></em></p>
  678. <p><a data-banner_id="21" data-campaign_id="11" href="http://www.telerik.com/platform" target="_blank" class="acc_banner_link "><img src="http://developer.telerik.com/wp-content/uploads/ac_uploads/1177154.jpg" width="570" height="100"/></a></p>
  679. </article>
  680. </section>
  681. <nav id="jumpto">
  682. <p>
  683. <a href="/david/blog/">Accueil du blog</a> |
  684. <a href="http://developer.telerik.com/featured/lazy-loading-images-on-the-web/">Source originale</a> |
  685. <a href="/david/stream/2019/">Accueil du flux</a>
  686. </p>
  687. </nav>
  688. <footer>
  689. <div>
  690. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  691. <p>
  692. Bonjour/Hi!
  693. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  694. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  695. </p>
  696. <p>
  697. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  698. </p>
  699. <p>
  700. Voici quelques articles choisis :
  701. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  702. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  703. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  704. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  705. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  706. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  707. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  708. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  709. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  710. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  711. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  712. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  713. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  714. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  715. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  716. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  717. </p>
  718. <p>
  719. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  720. </p>
  721. <p>
  722. Je ne traque pas ta navigation mais mon
  723. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  724. conserve des logs d’accès.
  725. </p>
  726. </div>
  727. </footer>
  728. <script type="text/javascript">
  729. ;(_ => {
  730. const jumper = document.getElementById('jumper')
  731. jumper.addEventListener('click', e => {
  732. e.preventDefault()
  733. const anchor = e.target.getAttribute('href')
  734. const targetEl = document.getElementById(anchor.substring(1))
  735. targetEl.scrollIntoView({behavior: 'smooth'})
  736. })
  737. })()
  738. </script>