index.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>three.js manual</title>
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <link rel="shortcut icon" href="../files/favicon_white.ico" media="(prefers-color-scheme: dark)"/>
  8. <link rel="shortcut icon" href="../files/favicon.ico" media="(prefers-color-scheme: light)" />
  9. <link rel="stylesheet" type="text/css" href="../files/main.css">
  10. <style>
  11. /*
  12. note: I don't know why the docs page is the way it is but it would
  13. seem to make sense for the iframe to be the size you want the content
  14. rather then every piece of content be responsible for knowing how to
  15. adjust itself to handle the left panel?
  16. */
  17. iframe {
  18. padding-left: var(--panel-width);
  19. }
  20. @media all and ( max-width: 640px ) {
  21. iframe {
  22. padding-left: 0;
  23. }
  24. }
  25. </style>
  26. <!-- console sandbox -->
  27. <script src="../build/three.min.js" async defer></script>
  28. </head>
  29. <body>
  30. <div id="panel">
  31. <div id="header">
  32. <h1><a href="https://threejs.org">three.js</a></h1>
  33. <div id="expandButton"></div>
  34. </div>
  35. <div id="panelScrim"></div>
  36. <div id="contentWrapper">
  37. <div id="inputWrapper">
  38. <input placeholder="" type="text" id="filterInput" autocorrect="off" autocapitalize="off" spellcheck="false" />
  39. <div id="exitSearchButton"></div>
  40. <select id="language">
  41. <option value="en">en</option>
  42. <option value="fr">fr</option>
  43. <option value="ru">ru</option>
  44. <option value="ko">한국어</option>
  45. <option value="zh_ch">中文</option>
  46. <option value="ja">日本語</option>
  47. </select>
  48. </div>
  49. <div id="content"></div>
  50. </div>
  51. </div>
  52. <iframe name="viewer"></iframe>
  53. <script>
  54. const panel = document.querySelector( '#panel' );
  55. const content = document.querySelector( '#content' );
  56. const expandButton = document.querySelector( '#expandButton' );
  57. const exitSearchButton = document.querySelector( '#exitSearchButton' );
  58. const panelScrim = document.querySelector( '#panelScrim' );
  59. const filterInput = document.querySelector( '#filterInput' );
  60. let iframe = document.querySelector( 'iframe' );
  61. const pageProperties = {};
  62. const titles = {};
  63. const categoryElements = [];
  64. let navigation;
  65. init();
  66. async function init() {
  67. const list = await ( await fetch( 'list.json' ) ).json();
  68. const hash = window.location.hash.substring( 1 );
  69. // Localization
  70. let language = 'en';
  71. const hashLanguage = /^(.*?)\//.exec( hash );
  72. if ( hashLanguage ) {
  73. language = hashLanguage[ 1 ];
  74. }
  75. const languageSelect = document.querySelector( '#language' );
  76. languageSelect.value = language;
  77. languageSelect.addEventListener( 'change', function () {
  78. setLanguage( this.value );
  79. } );
  80. function setLanguage( value ) {
  81. language = value;
  82. createNavigation( list, language );
  83. updateFilter();
  84. autoChangeUrlLanguage( language );
  85. }
  86. // Functionality for hamburger button (on small devices)
  87. expandButton.onclick = function ( event ) {
  88. event.preventDefault();
  89. panel.classList.toggle( 'open' );
  90. };
  91. panelScrim.onclick = function ( event ) {
  92. event.preventDefault();
  93. panel.classList.toggle( 'open' );
  94. };
  95. // Functionality for search/filter input field
  96. filterInput.onfocus = function () {
  97. panel.classList.add( 'searchFocused' );
  98. };
  99. filterInput.onblur = function () {
  100. if ( filterInput.value === '' ) {
  101. panel.classList.remove( 'searchFocused' );
  102. }
  103. };
  104. filterInput.oninput = function () {
  105. updateFilter();
  106. };
  107. exitSearchButton.onclick = function () {
  108. filterInput.value = '';
  109. updateFilter();
  110. panel.classList.remove( 'searchFocused' );
  111. };
  112. // Activate content and title change on browser navigation
  113. window.onpopstate = createNewIframe;
  114. // Create the navigation panel and configure the iframe
  115. createNavigation( list, language );
  116. createNewIframe();
  117. // Handle search query
  118. filterInput.value = extractQuery();
  119. if ( filterInput.value !== '' ) {
  120. panel.classList.add( 'searchFocused' );
  121. updateFilter();
  122. }
  123. }
  124. // Navigation Panel
  125. function createLink( pageName, pageURL ) {
  126. const link = document.createElement( 'a' );
  127. const url = new URL(pageURL, window.location.href);
  128. url.pathname += '.html';
  129. link.href = url.href;
  130. link.textContent = pageName;
  131. link.setAttribute( 'target', 'viewer' );
  132. link.addEventListener( 'click', function ( event ) {
  133. if ( event.button !== 0 || event.ctrlKey || event.altKey || event.metaKey ) return;
  134. window.location.hash = pageURL;
  135. panel.classList.remove( 'open' );
  136. content.querySelectorAll( 'a' ).forEach( function ( item ) {
  137. item.classList.remove( 'selected' );
  138. } );
  139. link.classList.add( 'selected' );
  140. } );
  141. return link;
  142. }
  143. function createNavigation( list, language ) {
  144. if ( navigation !== undefined ) {
  145. content.removeChild( navigation );
  146. }
  147. // Create the navigation panel using data from list.js
  148. navigation = document.createElement( 'div' );
  149. content.appendChild( navigation );
  150. if ( language === 'ar' ) {
  151. navigation.style.direction = 'rtl';
  152. }
  153. const localList = list[ language ];
  154. const selectedPage = window.location.hash.substring( 1 );
  155. for ( const section in localList ) {
  156. // Create sections
  157. const categories = localList[ section ];
  158. const sectionHead = document.createElement( 'h2' );
  159. sectionHead.textContent = section;
  160. navigation.appendChild( sectionHead );
  161. for ( const category in categories ) {
  162. // Create categories
  163. const pages = categories[ category ];
  164. const categoryContainer = document.createElement( 'div' );
  165. navigation.appendChild( categoryContainer );
  166. const categoryHead = document.createElement( 'h3' );
  167. categoryHead.textContent = category;
  168. categoryContainer.appendChild( categoryHead );
  169. const categoryContent = document.createElement( 'ul' );
  170. categoryContainer.appendChild( categoryContent );
  171. for ( const pageName in pages ) {
  172. // Create page links
  173. const pageURL = pages[ pageName ];
  174. // Localisation
  175. const listElement = document.createElement( 'li' );
  176. categoryContent.appendChild( listElement );
  177. const linkElement = createLink( pageName, pageURL );
  178. listElement.appendChild( linkElement );
  179. // select current page
  180. if ( pageURL === selectedPage ) {
  181. linkElement.classList.add( 'selected' );
  182. }
  183. // Gather the main properties for the current subpage
  184. pageProperties[ pageName ] = {
  185. section: section,
  186. category: category,
  187. pageURL: pageURL,
  188. linkElement: linkElement
  189. };
  190. // Gather the document titles (used for easy access on browser navigation)
  191. titles[ pageURL ] = pageName;
  192. }
  193. // Gather the category elements for easy access on filtering
  194. categoryElements.push( categoryContent );
  195. }
  196. }
  197. }
  198. // Auto change language url. If a reader open a document in English, when he click "zh", the document he read will auto change into Chinese version
  199. function autoChangeUrlLanguage( language ) {
  200. const hash = location.hash;
  201. if ( hash === '' ) return;
  202. const docLink = hash.substr( hash.indexOf( '/' ) );
  203. location.href = '#' + language + docLink;
  204. }
  205. // Filtering
  206. function extractQuery() {
  207. const search = window.location.search;
  208. if ( search.indexOf( '?q=' ) !== - 1 ) {
  209. return decodeURI( search.substr( 3 ) );
  210. }
  211. return '';
  212. }
  213. function updateFilter() {
  214. let v = filterInput.value.trim();
  215. v = v.replace( /\s+/gi, ' ' ); // replace multiple whitespaces with a single one
  216. if ( v !== '' ) {
  217. window.history.replaceState( {}, '', '?q=' + v + window.location.hash );
  218. } else {
  219. window.history.replaceState( {}, '', window.location.pathname + window.location.hash );
  220. }
  221. //
  222. const regExp = new RegExp( filterInput.value, 'gi' );
  223. for ( let pageName in pageProperties ) {
  224. const linkElement = pageProperties[ pageName ].linkElement;
  225. const categoryClassList = linkElement.parentElement.classList;
  226. const filterResults = pageName.match( regExp );
  227. if ( filterResults !== null && filterResults.length > 0 ) {
  228. pageName = pageName.replaceAll( regExp, '<b>$&</b>' );
  229. categoryClassList.remove( 'hidden' );
  230. linkElement.innerHTML = pageName;
  231. } else {
  232. // Hide all non-matching page names
  233. categoryClassList.add( 'hidden' );
  234. }
  235. }
  236. displayFilteredPanel();
  237. }
  238. function displayFilteredPanel() {
  239. // Show/hide categories depending on their content
  240. // First check if at least one page in this category is not hidden
  241. categoryElements.forEach( function ( category ) {
  242. const pages = category.children;
  243. const pagesLength = pages.length;
  244. const sectionClassList = category.parentElement.classList;
  245. let hideCategory = true;
  246. for ( let i = 0; i < pagesLength; i ++ ) {
  247. const pageClassList = pages[ i ].classList;
  248. if ( ! pageClassList.contains( 'hidden' ) ) {
  249. hideCategory = false;
  250. }
  251. }
  252. // If and only if all page names are hidden, hide the whole category
  253. if ( hideCategory ) {
  254. sectionClassList.add( 'hidden' );
  255. } else {
  256. sectionClassList.remove( 'hidden' );
  257. }
  258. } );
  259. }
  260. // Routing
  261. function setUrl( href ) { // eslint-disable-line no-undef
  262. // yea I know this is hacky.
  263. const re = /^(\/(?:manual\/|docs\/#?))(.*?)$/;
  264. const url = new URL(href);
  265. if (url.origin === window.location.origin) {
  266. const hrefNoOrigin = url.href.substr(url.origin.length);
  267. const m = re.exec(hrefNoOrigin);
  268. const [,base, path] = m;
  269. if (base.includes('manual')) {
  270. const newHash = `#${path.replace('.html', '')}`;
  271. // Only create new iframe if we're actually changing pages.
  272. // We could just be going to an anchor on the same page.
  273. const newPrefix = newHash.split('#')[1];
  274. const oldPrefix = window.location.hash.split('#')[1];
  275. if (newPrefix === oldPrefix) {
  276. const newUrl = new URL(window.location.href);
  277. newUrl.hash = newHash;
  278. window.history.pushState({}, '', newUrl.href);
  279. } else {
  280. window.location.hash = newHash;
  281. createNewIframe();
  282. }
  283. return;
  284. }
  285. }
  286. window.location.href = href;
  287. }
  288. function setTitle(title) { // eslint-disable-line no-undef
  289. document.title = `${title} - three.js manual`;
  290. }
  291. function createNewIframe() {
  292. // Change the content displayed in the iframe
  293. // First separate the member part of the fragment (if existing)
  294. const hash = window.location.hash.substring( 1 );
  295. const splitHash = decomposePageName( hash, '.', '#' );
  296. // Creating a new Iframe instead of assigning a new src is
  297. // a cross-browser solution to allow normal browser navigation;
  298. // - only assigning a new src would result in two history states each time.
  299. // Note: iframe.contentWindow.location.replace(hash) should work, too,
  300. // but it doesn't work in Edge with links from the subpages!
  301. const oldIframe = iframe;
  302. iframe = oldIframe.cloneNode();
  303. if ( hash ) {
  304. // We can have 2 hashes. One for the main page, one for the page it's referencing
  305. // In other words
  306. // #en/somePage#someSectionOfPage
  307. const subHash = splitHash[0].indexOf('#');
  308. let src;
  309. if (subHash >= 0) {
  310. const beforeSubHash = splitHash[0].substr(0, subHash);
  311. const afterSubHash = splitHash[0].substr(subHash);
  312. src = `${beforeSubHash}.html${afterSubHash}${splitHash[ 1 ]}`;
  313. } else {
  314. src = splitHash[ 0 ] + '.html' + splitHash[ 1 ];
  315. }
  316. iframe.src = src; // lgtm[js/client-side-unvalidated-url-redirection]
  317. iframe.style.display = 'unset';
  318. } else {
  319. iframe.src = '';
  320. iframe.style.display = 'none';
  321. }
  322. document.body.replaceChild( iframe, oldIframe );
  323. }
  324. function decomposePageName( pageName, oldDelimiter, newDelimiter ) {
  325. // Helper function for separating the member (if existing) from the pageName
  326. // For example: 'Geometry.morphTarget' can be converted to
  327. // ['Geometry', '.morphTarget'] or ['Geometry', '#morphTarget']
  328. // Note: According RFC 3986 no '#' allowed inside of an URL fragment!
  329. let parts = [];
  330. const dotIndex = pageName.indexOf( oldDelimiter );
  331. if ( dotIndex !== - 1 ) {
  332. parts = pageName.split( oldDelimiter );
  333. parts[ 1 ] = newDelimiter + parts[ 1 ];
  334. } else {
  335. parts[ 0 ] = pageName;
  336. parts[ 1 ] = '';
  337. }
  338. return parts;
  339. }
  340. //
  341. console.log( [
  342. ' __ __',
  343. ' __/ __\\ / __\\__ ____ _____ _____',
  344. '/ __/ /\\/ / /___\\/ ____\\/ _____\\/ _____\\',
  345. '\\/_ __/ / _ / / __/ / __ / / __ /_ __ _____',
  346. '/ / / / / / / / / / / / ___/ / ___/\\ _\\/ __\\/ _____\\',
  347. '\\/__/ \\/__/\\/__/\\/__/ \\/_____/\\/_____/\\/__/ / / / ___/',
  348. ' / __/ / \\__ \\',
  349. ' \\/____/\\/_____/'
  350. ].join( '\n' ) );
  351. </script>
  352. </body>
  353. </html>
粤ICP备19079148号