UNPKG

@google/model-viewer

Version:

Easily display interactive 3D models on the web and in AR!

366 lines (314 loc) 7.76 kB
/* @license * Copyright 2019 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import CloseIcon from './assets/close-material-svg.js'; import ControlsPrompt from './assets/controls-svg.js'; import ARGlyph from './assets/view-in-ar-material-svg.js'; const template = document.createElement('template'); template.innerHTML = ` <style> :host { display: block; position: relative; contain: strict; width: 300px; height: 150px; } /* NOTE: This ruleset is our integration surface area with the * :focus-visible polyfill. * * @see https://github.com/WICG/focus-visible/pull/196 */ :host([data-js-focus-visible]:focus:not(.focus-visible)), :host([data-js-focus-visible]) :focus:not(.focus-visible) { outline: none; } .container { position: relative; } .userInput { width: 100%; height: 100%; display: block; position: relative; overflow: hidden; } canvas { position: absolute; display: none; pointer-events: none; /* NOTE(cdata): Chrome 76 and below apparently have a bug * that causes our canvas not to display pixels unless it is * on its own render layer * @see https://github.com/google/model-viewer/pull/755#issuecomment-536597893 */ transform: translateZ(0); } canvas.show { display: block; } /* Adapted from HTML5 Boilerplate * * @see https://github.com/h5bp/html5-boilerplate/blob/ceb4620c78fc82e13534fc44202a3f168754873f/dist/css/main.css#L122-L133 */ .screen-reader-only { border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; white-space: nowrap; width: 1px; } .slot { position: absolute; pointer-events: none; top: 0; left: 0; width: 100%; height: 100%; } .slot > * { pointer-events: initial; } .annotation-wrapper ::slotted(*) { opacity: var(--max-hotspot-opacity, 1); transition: opacity 0.3s; } .pointer-tumbling .annotation-wrapper ::slotted(*) { pointer-events: none; } .annotation-wrapper ::slotted(*) { pointer-events: initial; } .annotation-wrapper.hide ::slotted(*) { opacity: var(--min-hotspot-opacity, 0.25); } .slot.poster { opacity: 0; transition: opacity 0.3s 0.3s; background-color: inherit; } .slot.poster.show { opacity: 1; transition: none; } .slot.poster > * { pointer-events: initial; } .slot.poster:not(.show) > * { pointer-events: none; } #default-poster { width: 100%; height: 100%; /* The default poster is a <button> so we need to set display * to prevent it from being affected by text-align: */ display: block; position: absolute; border: none; padding: 0; background-size: contain; background-repeat: no-repeat; background-position: center; background-color: var(--poster-color, #fff); background-image: var(--poster-image, none); } #default-progress-bar { display: block; position: relative; width: 100%; height: 100%; pointer-events: none; overflow: hidden; } #default-progress-bar > .mask { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--progress-mask, #fff); transition: opacity 0.3s; opacity: 0.2; } #default-progress-bar > .bar { position: absolute; top: 0; left: 0; width: 100%; height: var(--progress-bar-height, 5px); transition: transform 0.09s; transform-origin: top left; transform: scaleX(0); overflow: hidden; } #default-progress-bar > .bar:before { content: ''; display: block; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--progress-bar-color, rgba(0, 0, 0, 0.4)); transition: none; transform-origin: top left; transform: translateY(0); } #default-progress-bar > .bar.hide:before { transition: transform 0.3s 1s; transform: translateY(-100%); } .slot.interaction-prompt { display: var(--interaction-prompt-display, flex); position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; align-items: center; justify-content: center; opacity: 0; will-change: opacity; overflow: hidden; transition: opacity 0.3s; } .slot.interaction-prompt.visible { opacity: 1; } .slot.interaction-prompt > .animated-container { will-change: transform, opacity; } .slot.interaction-prompt > * { pointer-events: none; } .slot.ar-button { -moz-user-select: none; -webkit-tap-highlight-color: transparent; user-select: none; display: var(--ar-button-display, block); } .slot.ar-button:not(.enabled) { display: none; } .fab { display: flex; align-items: center; justify-content: center; box-sizing: border-box; width: 40px; height: 40px; cursor: pointer; background-color: #fff; box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.15); border-radius: 100px; } .fab > * { opacity: 0.87; } #default-ar-button { position: absolute; bottom: 16px; right: 16px; transform: scale(var(--ar-button-scale, 1)); transform-origin: bottom right; } .slot.default { pointer-events: none; } .slot.progress-bar { pointer-events: none; } .slot.exit-webxr-ar-button { pointer-events: none; } .slot.exit-webxr-ar-button:not(.enabled) { display: none; } #default-exit-webxr-ar-button { display: flex; align-items: center; justify-content: center; position: absolute; top: 16px; right: 16px; width: 40px; height: 40px; box-sizing: border-box; } #default-exit-webxr-ar-button > svg { fill: #fff; } </style> <div class="container"> <div class="userInput" tabindex="0" role="img" aria-label="A depiction of a 3D model" aria-live="polite"> <canvas></canvas> </div> <!-- NOTE(cdata): We need to wrap slots because browsers without ShadowDOM will have their <slot> elements removed by ShadyCSS --> <div class="slot poster"> <slot name="poster"> <button type="button" id="default-poster" aria-hidden="true" aria-label="Activate to view in 3D!"></button> </slot> </div> <div class="slot ar-button"> <slot name="ar-button"> <a id="default-ar-button" class="fab" tabindex="2" aria-label="View this 3D model up close"> ${ARGlyph} </a> </slot> </div> <div class="slot interaction-prompt"> <div class="animated-container" part="interaction-prompt"> <slot name="interaction-prompt" aria-hidden="true"> ${ControlsPrompt} </slot> </div> </div> <div class="slot default"> <slot></slot> <div class="slot progress-bar"> <slot name="progress-bar"> <div id="default-progress-bar" aria-hidden="true"> <div class="mask"></div> <div class="bar"></div> </div> </slot> </div> <div class="slot exit-webxr-ar-button"> <slot name="exit-webxr-ar-button"> <a id="default-exit-webxr-ar-button" tabindex="3" aria-label="Exit AR" aria-hidden="true"> ${CloseIcon} </a> </slot> </div> </div> </div>`; export default template; export const makeTemplate = (tagName: string) => { const clone = document.createElement('template'); clone.innerHTML = template.innerHTML; if ((window as any).ShadyCSS) { (window as any).ShadyCSS.prepareTemplate(clone, tagName); } return clone; };