A place to cache linked articles (think custom and personal wayback machine)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

index.html 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  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>History API - Dive Into HTML5 (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://diveintohtml5.info/history.html">
  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. History API - Dive Into HTML5 (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://diveintohtml5.info/history.html">Source originale du contenu</a></h3>
  445. <p class="f">The browser location bar is perhaps the geekiest mainstream piece of user interface in the world. There are <abbr>URL</abbr>s on billboards, on the sides of trains, and even in street graffiti. Combined with the back button — easily the most important button in the browser — you have a powerful way to go forward and backward through the vast set of intertwingled resources called the Web.
  446. </p>
  447. <p>The <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html"><abbr>HTML5</abbr> history <abbr>API</abbr></a> is a standardized way to manipulate the browser history via script. Part of this <abbr>API</abbr> — navigating the history — has been available in previous versions of <abbr>HTML</abbr>. The new parts in <abbr>HTML5</abbr> include a way to add entries to the browser history, to visibly change the <abbr>URL</abbr> in the browser location bar (without triggering a page refresh), and an event that fires when those entries are removed from the stack by the user pressing the browser’s back button. This means that the <abbr>URL</abbr> in the browser location bar can continue to do its job as a unique identifier for the current resource, even in script-heavy applications that don’t ever perform a full page refresh.
  448. </p>
  449. <h2 id="why">The Why</h2>
  450. <p class="ss"><img src="http://diveintohtml5.info/i/openclipart.org_johnny_automatic_demon_reading_Stewart_Orr.png" alt="demon reading book" width="218" height="231"/>
  451. </p>
  452. <p>Why would you manually manipulate the browser location? After all, a simple link can navigate to a new <abbr>URL</abbr>; that’s the way the web has worked for 20 years. And it will continue to work that way. This <abbr>API</abbr> doesn’t try to subvert the web. Just the opposite. In recent years, web developers have found new and exciting ways of subverting the web without any help from emerging standards. The <abbr>HTML5</abbr> history <abbr>API</abbr> is actually designed to ensure that <abbr>URL</abbr>s continue to be useful in script-heavy web applications.
  453. </p>
  454. <p>Going back to first principles, what does a <abbr>URL</abbr> do? It identifies a unique resource. You can link to it directly; you can bookmark it; search engines can index it; you can copy and paste it and email it to someone else, who can click it and end up seeing the same resource you saw originally. These are all excellent qualities. <abbr>URL</abbr>s matter.
  455. </p>
  456. <p>So we want unique resources to have unique <abbr>URL</abbr>s. But at the same time, browsers have always had a fundamental limitation: if you change the <abbr>URL</abbr>, even through script, it triggers a roundtrip to the remote web server and a full page refresh. This takes time and resources, and it seems especially wasteful when you are navigating to a page that is substantially similar to the current page. Everything on the new page gets downloaded, even the parts that are exactly the same as the current page. There is no way tell a browser to change the <abbr>URL</abbr> but only download half a page.
  457. </p>
  458. <p>The <abbr>HTML5</abbr> history <abbr>API</abbr> lets you do this. Instead of triggering a full page refresh, you can use script to, in essence, download half a page. This illusion is tricky to pull off, and it requires some work on your part. Are you watching closely?
  459. </p>
  460. <p class="ss"><img src="http://diveintohtml5.info/i/openclipart.org_johnny_automatic_card_trick.png" width="287" height="238" alt="magician performing a card trick"/>
  461. </p>
  462. <p id="illusion">Let’s say you have two pages, page A and page B. The two pages are 90% identical; only 10% of the page content is different. The user navigates to page A, then tries to navigate to page B. But instead of triggering a full page refresh, you interrupt this navigation and do the following steps manually:
  463. </p>
  464. <ol>
  465. <li><em>Load the 10% of the page</em> from page B that is different from page A (probably using <code>XMLHttpRequest</code>). This will require some server-side changes to your web application. You will need to write code to return just the 10% of page B that is different from page A. This can be a hidden <abbr>URL</abbr> or query parameter that the end user would not normally see.
  466. </li><li><em>Swap in the changed content</em> (using <code>innerHTML</code> or other <abbr>DOM</abbr> methods). You may also need to reset any event handlers on elements within the swapped-in content.
  467. </li><li><em>Update the browser location bar</em> with the <abbr>URL</abbr> of page B, using a particular method from the <abbr>HTML5</abbr> history <abbr>API</abbr> that I’ll show you in a moment.
  468. </li></ol>
  469. <p>At the end of this illusion (if you executed it correctly), the browser ends up with a <abbr>DOM</abbr> that is identical to page B, just as if you had navigated to page B directly. The browser location bar ends up with a <abbr>URL</abbr> that is identical to page B, just as if you had navigated to page B directly. But you never really did navigate to page B, and you never did a full page refresh. That’s the illusion. But because the “compiled” page looks the same as page B and has the same <abbr>URL</abbr> as page B, the user should never notice the difference (nor appreciate all your hard work micromanaging their experience).
  470. </p>
  471. <p class="a rotatedFloralHeartBullet">
  472. </p>
  473. <h2 id="how">The How</h2>
  474. <p>The <abbr>HTML5</abbr> history <abbr>API</abbr> is just a handful of methods on the <code>window.history</code> object, plus one event on the <code>window</code> object. You can use these to <a href="http://diveintohtml5.info/detect.html#history">detect support for the history <abbr>API</abbr></a>. Support is currently limited to the very latest versions of a few browsers, putting these techniques squarely in the “progressive enhancement” camp.
  475. </p>
  476. <table class="bc">
  477. <caption>history.pushState support</caption>
  478. <thead>
  479. <tr><th title="Internet Explorer">IE</th><th title="Mozilla Firefox">Firefox</th><th title="Apple Safari">Safari</th><th title="Google Chrome">Chrome</th><th>Opera</th><th>iPhone</th><th>Android
  480. </th></tr></thead><tbody>
  481. <tr><td>·</td><td>4.0+</td><td>5.0+</td><td>8.0+</td><td>11.50+</td><td>4.2.1+</td><td>·
  482. </td></tr></tbody></table>
  483. <p><a href="http://diveintohtml5.info/examples/history/fer.html">dive into dogs</a> is a straightforward but non-trivial example of using the <abbr>HTML5</abbr> history <abbr>API</abbr>. It demonstrates a common pattern: a long article with an associated inline photo gallery. In a supported browser, navigating the Next and Previous links in the photo gallery will update the photo in place <em>and update the <abbr>URL</abbr> in the browser location bar</em>, without triggering a full page refresh. In unsupported browsers — or, indeed, supported browsers where the user has disabled scripting — the links simply function as regular links, taking you to a new page with a full page refresh.
  484. </p>
  485. <p id="gallery-markup">Let’s dig into the <a href="http://diveintohtml5.info/examples/history/fer.html">dive into dogs</a> demo and see how it works. This is the relevant markup for a single photo:
  486. </p>
  487. <p class="legend top"><span class="arrow anticlockwiseTopSemicircleArrow"/> The pledge
  488. </p>
  489. <pre><code>&lt;aside id="gallery"&gt;
  490. &lt;p class="photonav"&gt;
  491. &lt;a id="photonext" href="http://diveintohtml5.info/casey.html"&gt;Next &amp;gt;&lt;/a&gt;
  492. &lt;a id="photoprev" href="http://diveintohtml5.info/adagio.html"&gt;&amp;lt; Previous&lt;/a&gt;
  493. &lt;/p&gt;
  494. &lt;figure id="photo"&gt;
  495. &lt;img id="photoimg" src="http://diveintohtml5.info/gallery/1972-fer-500.jpg"
  496. alt="Fer" width="500" height="375"&gt;
  497. &lt;figcaption&gt;Fer, 1972&lt;/figcaption&gt;
  498. &lt;/figure&gt;
  499. &lt;/aside&gt;</code></pre>
  500. <p>Nothing unusual there. The photo itself is an <code>&lt;img&gt;</code> inside a <code>&lt;figure&gt;</code>, the links are just regular <code>&lt;a&gt;</code> elements, and the entire thing is wrapped in an <code>&lt;aside&gt;</code>. It’s important that these are just regular links that actually work. All the code that follows is behind a <a href="http://diveintohtml5.info/detect.html#history">detection script</a>. If the user is using an unsupported browser, none of our fancy history <abbr>API</abbr> code will ever be executed. And of course, there’s always some users with scripting disabled altogether.
  501. </p>
  502. <p>The main driver function gets each of these links and passes it to a function, <code>addClicker()</code>, which does the actual work of setting up the custom <code>click</code> handler.
  503. </p>
  504. <pre><code>function setupHistoryClicks() {
  505. addClicker(document.getElementById("photonext"));
  506. addClicker(document.getElementById("photoprev"));
  507. }</code></pre>
  508. <p>This is the <code>addClicker()</code> function. It takes an <code>&lt;a&gt;</code> element and adds a <code>click</code> handler. And within this <code>click</code> handler is where it gets interesting.
  509. </p>
  510. <pre><code>function addClicker(link) {
  511. link.addEventListener("click", function(e) {
  512. swapPhoto(link.href);
  513. history.pushState(null, null, link.href);
  514. e.preventDefault();
  515. }, false);
  516. }</code></pre>
  517. <p class="legend right"><span class="arrow leftwardsWaveArrow"/> Interesting
  518. </p>
  519. <p>The <code>swapPhoto()</code> function performs the first two steps of our <a href="http://diveintohtml5.info/#illusion">three-step illusion</a>. The first half of the <code>swapPhoto()</code> function takes part of the <abbr>URL</abbr> of the navigation link itself — <code>casey.html</code>, <code>adagio.html</code>, <i>&amp;</i>c. — and constructs a <abbr>URL</abbr> to a hidden page that contains nothing but the markup required by the next photo.
  520. </p>
  521. <pre><code>function swapPhoto(href) {
  522. var req = new XMLHttpRequest();
  523. req.open("GET",
  524. "http://diveintohtml5.info/examples/history/gallery/" +
  525. href.split("/").pop(),
  526. false);
  527. req.send(null);</code></pre>
  528. <p>Here is a sample of the markup returned by <code><a href="http://diveintohtml5.info/examples/history/gallery/casey.html">http://diveintohtml5.info/examples/history/gallery/casey.html</a></code>. (You can verify this in your browser by visiting that <abbr>URL</abbr> directly.)
  529. </p>
  530. <pre><code>&lt;p class="photonav"&gt;
  531. &lt;a id="photonext" href="http://diveintohtml5.info/brandy.html"&gt;Next &amp;gt;&lt;/a&gt;
  532. &lt;a id="photoprev" href="http://diveintohtml5.info/fer.html"&gt;&amp;lt; Previous&lt;/a&gt;
  533. &lt;/p&gt;
  534. &lt;figure id="photo"&gt;
  535. &lt;img id="photoimg" src="http://diveintohtml5.info/gallery/1984-casey-500.jpg"
  536. alt="Casey" width="500" height="375"&gt;
  537. &lt;figcaption&gt;Casey, 1984&lt;/figcaption&gt;
  538. &lt;/figure&gt;</code></pre>
  539. <p>Does that look familiar? It should. It’s the <a href="http://diveintohtml5.info/#gallery-markup">same basic markup that the original page used</a> to display the first photo.
  540. </p>
  541. <p>The second half of the <code>swapPhoto()</code> function performs the second step of our <a href="http://diveintohtml5.info/#illusion">three-step illusion</a>: inserting this newly downloaded markup into the current page. Remember that there is an <code>&lt;aside&gt;</code> wrapping the entire figure, photo, and caption. So inserting the new photo markup is a one-liner, setting the <code>innerHTML</code> property of the <code>&lt;aside&gt;</code> to the <code>responseText</code> property returned from <code>XMLHttpRequest</code>.
  542. </p>
  543. <pre><code> if (req.status == 200) {
  544. document.getElementById("gallery").innerHTML = req.responseText;
  545. setupHistoryClicks();
  546. return true;
  547. }
  548. return false;
  549. }</code></pre>
  550. <p>(Also notice the call to <code>setupHistoryClicks()</code>. This is necessary to reset the custom <code>click</code> event handlers on the newly inserted navigation links. Setting <code>innerHTML</code> wipes out any trace of the old links and their event handlers.)
  551. </p>
  552. <p>Now, let’s go back to the <code>addClicker()</code> function. After successfully swapping out the photo, there’s one more step in our <a href="http://diveintohtml5.info/#illusion">three-step illusion</a>: setting the <abbr>URL</abbr> in the browser location bar without refreshing the page.
  553. </p>
  554. <p class="legend top"><span class="arrow anticlockwiseTopSemicircleArrow"/> The turn
  555. </p>
  556. <pre><code>history.pushState(null, null, link.href);</code></pre>
  557. <p>The <code>history.pushState()</code> function takes three parameters:
  558. </p>
  559. <ol>
  560. <li><code>state</code> can be any <abbr>JSON</abbr> data structure. It is passed back to the <code>popstate</code> event hander, which you’ll learn about in just a moment. We don’t need to track any state in this demo, so I’ve left it as <code>null</code>.
  561. </li><li><code>title</code> can be any string. This parameter is currently unused by major browsers. If you want to set the page title, you should store it in the <code>state</code> argument and set it manually in your <code>popstate</code> callback.
  562. </li><li><code>url</code> can be, well, any <abbr>URL</abbr>. This is the <abbr>URL</abbr> you want to appear in the browser’s location bar.
  563. </li></ol>
  564. <p>Calling <code>history.pushState</code> will immediately change the <abbr>URL</abbr> in the browser’s location bar. So is that the end of the illusion? Well, not quite. We still need to talk about what happens when the user presses the all-important back button.
  565. </p>
  566. <p>Normally when the user navigates to a new page (with a full page refresh), the browser pushes the new <abbr>URL</abbr> onto its history stack and downloads and draws the new page. When the user presses the back button, the browser pops one page off its history stack and redraws the previous page. But what happens now that you’ve short-circuited this navigation to avoid a full page refresh? Well, you’ve faked “moving forward” to a new <abbr>URL</abbr>, so now you also need to fake “moving backward” to the previous <abbr>URL</abbr>. And the key to faking “moving backwards” is the <code>popstate</code> event.
  567. </p>
  568. <p class="legend top"><span class="arrow anticlockwiseTopSemicircleArrow"/> The prestige
  569. </p>
  570. <pre><code>window.addEventListener("popstate", function(e) {
  571. swapPhoto(location.pathname);
  572. });</code></pre>
  573. <p>After you’ve used the <code>history.pushState()</code> function to push a fake <abbr>URL</abbr> onto the browser’s history stack, when the user presses the back button, the browser will fire a <code>popstate</code> event on the <code>window</code> object. This is your chance to complete the illusion once and for all. Because making something disappear isn't enough; you have to bring it back.
  574. </p>
  575. <p>In this demonstration, “bringing it back” is as simple as swapping in the original photo, which we do by calling the <code>swapPhoto()</code> with the current location. By the time your <code>popstate</code> callback is called, the <abbr>URL</abbr> visible in the browser’s location bar has been changed to the previous <abbr>URL</abbr>. Also, the global <code>location</code> property has already been updated with the previous <abbr>URL</abbr>.
  576. </p>
  577. <p>To help you visualize this, let’s step through the entire illusion from the beginning to the end:
  578. </p>
  579. <ul>
  580. <li>User loads <code><a href="http://diveintohtml5.info/examples/history/fer.html">http://diveintohtml5.info/examples/history/fer.html</a></code>, sees story and a photo of Fer.
  581. </li><li>User clicks the link labeled “Next,” an <code>&lt;a&gt;</code> element whose <code>href</code> property is <code><a href="http://diveintohtml5.info/examples/history/casey.html">http://diveintohtml5.info/examples/history/casey.html</a></code>.
  582. </li><li>Instead of navigating <code><a href="http://diveintohtml5.info/examples/history/casey.html">http://diveintohtml5.info/examples/history/casey.html</a></code> with a full page refresh, the custom <code>click</code> handler on the <code>&lt;a&gt;</code> element traps the click and executes its own code.
  583. </li><li>Our custom <code>click</code> handler calls the <code>swapPhoto()</code> function, which creates an <code>XMLHttpRequest</code> object to synchronously download the <abbr>HTML</abbr> snippet located at <code><a href="http://diveintohtml5.info/examples/history/gallery/casey.html">http://diveintohtml5.info/examples/history/<strong>gallery</strong>/casey.html</a></code>.
  584. </li><li>The <code>swapPhoto()</code> function sets the <code>innerHTML</code> property of the photo gallery wrapper (an <code>&lt;aside&gt;</code> element), thereby replacing the captioned photo of Fer with a captioned photo of Casey.
  585. </li><li>Finally, our custom <code>click</code> handler calls the <code>history.pushState()</code> function to manually change the <abbr>URL</abbr> in the browser’s location bar to <code><a href="http://diveintohtml5.info/examples/history/casey.html">http://diveintohtml5.info/examples/history/casey.html</a></code>.
  586. </li><li>User clicks the browser’s back button.
  587. </li><li>The browser notices that a <abbr>URL</abbr> has been manually pushed onto the history stack (by the <code>history.pushState()</code> function). Instead of navigating to the previous <abbr>URL</abbr> and redrawing the entire page, the browser simply updates the location bar to the previous <abbr>URL</abbr> (<code><a href="http://diveintohtml5.info/examples/history/fer.html">http://diveintohtml5.info/examples/history/fer.html</a></code>) and fires a <code>popstate</code> event.
  588. </li><li>Our custom <code>popstate</code> handler calls the <code>swapPhoto()</code> function again, this time with the previous <abbr>URL</abbr> that by now is already visible in the browser’s location bar.
  589. </li><li>Again using <code>XMLHttpRequest</code>, the <code>swapPhoto()</code> function downloads a snippet of <abbr>HTML</abbr> located at <code><a href="http://diveintohtml5.info/examples/history/gallery/fer.html">http://diveintohtml5.info/examples/history/<strong>gallery</strong>/fer.html</a></code> and sets the <code>innerHTML</code> property of the <code>&lt;aside&gt;</code> wrapper element, thereby replacing the captioned photo of Casey with a captioned photo of Fer.
  590. </li></ul>
  591. <p>The illusion is complete. All visible evidence (the content of the page, and the <abbr>URL</abbr> in the location bar) suggests to the user that they have navigated forward one page and backward one page. But no full page refresh ever occurred — it was all a meticulously executed illusion.
  592. </p>
  593. </article>
  594. </section>
  595. <nav id="jumpto">
  596. <p>
  597. <a href="/david/blog/">Accueil du blog</a> |
  598. <a href="http://diveintohtml5.info/history.html">Source originale</a> |
  599. <a href="/david/stream/2019/">Accueil du flux</a>
  600. </p>
  601. </nav>
  602. <footer>
  603. <div>
  604. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  605. <p>
  606. Bonjour/Hi!
  607. 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>
  608. 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>).
  609. </p>
  610. <p>
  611. 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>.
  612. </p>
  613. <p>
  614. Voici quelques articles choisis :
  615. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  616. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  617. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  618. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  619. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  620. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  621. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  622. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  623. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  624. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  625. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  626. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  627. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  628. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  629. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  630. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  631. </p>
  632. <p>
  633. 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>.
  634. </p>
  635. <p>
  636. Je ne traque pas ta navigation mais mon
  637. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  638. conserve des logs d’accès.
  639. </p>
  640. </div>
  641. </footer>
  642. <script type="text/javascript">
  643. ;(_ => {
  644. const jumper = document.getElementById('jumper')
  645. jumper.addEventListener('click', e => {
  646. e.preventDefault()
  647. const anchor = e.target.getAttribute('href')
  648. const targetEl = document.getElementById(anchor.substring(1))
  649. targetEl.scrollIntoView({behavior: 'smooth'})
  650. })
  651. })()
  652. </script>