@google/model-viewer
Version:
Easily display interactive 3D models on the web and in AR!
362 lines (315 loc) • 7.78 kB
JavaScript
/* @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) => {
const clone = document.createElement('template');
clone.innerHTML = template.innerHTML;
if (window.ShadyCSS) {
window.ShadyCSS.prepareTemplate(clone, tagName);
}
return clone;
};
//# sourceMappingURL=template.js.map