Skip to content

Commit

Permalink
Seemless poster transitions (google#890)
Browse files Browse the repository at this point in the history
* updated toBlob signature

* added idealAspect

* updated poster template

* fixed bugs

* fixed poster background

* added idealAspect test

* addressing feedback

* making test dpr-aware

* addressing feedback

* fixed poster background

* updated posters
  • Loading branch information
elalish authored Nov 18, 2019
1 parent 3be5503 commit 58db473
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 67 deletions.
Binary file modified examples/assets/poster-astronaut.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/assets/poster-astronaut2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/assets/poster-astronaut3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/assets/poster-astronaut4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/assets/poster-shishkebab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/assets/poster-sphere.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions examples/lazy-loading.html
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,12 @@ <h4></h4>
</div>
<example-snippet stamp-to="demo-container-5" highlight-as="html">
<template>
<model-viewer id="toggle-poster" reveal="interaction" auto-rotate poster="assets/poster-astronaut.png" src="assets/Astronaut.glb" alt="A 3D model of an astronaut"></model-viewer>
<model-viewer id="toggle-poster" reveal="interaction" auto-rotate poster="assets/poster-astronaut2.png" src="assets/Astronaut.glb" alt="A 3D model of an astronaut"></model-viewer>
<script>
const posters = ['poster-astronaut2.png', 'poster-astronaut3.png', 'poster-astronaut4.png'];
let i = 0;
setInterval(() =>
$('#toggle-poster').setAttribute('poster', `assets/${posters[i++ % 2]}`), 2000);
$('#toggle-poster').setAttribute('poster', `assets/${posters[++i % 3]}`), 2000);
</script>

</template>
Expand Down
1 change: 0 additions & 1 deletion examples/styles/examples.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
--button-size: 36px;
--icon-size: 28px;
--header-height: 72px;
--poster-color: #F5F5F5;
}

body {
Expand Down
18 changes: 7 additions & 11 deletions src/features/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,13 @@ export const EnvironmentMixin = <T extends Constructor<ModelViewerElementBase>>(
return;
}

const {backgroundImage, environmentImage} = this;
let {backgroundColor} = this;
const {backgroundImage, backgroundColor, environmentImage} = this;
// Set the container node's background color so that it matches
// the background color configured for the scene. It's important
// to do this because we round the size of the canvas off to the
// nearest pixel, so it is possible (indeed likely) that there is
// a marginal gap around one or two edges of the canvas.
this[$container].style.backgroundColor = backgroundColor;

if (this[$cancelEnvironmentUpdate] != null) {
this[$cancelEnvironmentUpdate]!();
Expand Down Expand Up @@ -148,18 +153,9 @@ export const EnvironmentMixin = <T extends Constructor<ModelViewerElementBase>>(
this[$scene].add(this[$scene].skyboxMesh);
} else {
this[$scene].remove(this[$scene].skyboxMesh);
if (!backgroundColor) {
backgroundColor = DEFAULT_BACKGROUND_COLOR;
}

const parsedColor = new Color(backgroundColor);
this[$scene].background = parsedColor;
// Set the container node's background color so that it matches
// the background color configured for the scene. It's important
// to do this because we round the size of the canvas off to the
// nearest pixel, so it is possible (indeed likely) that there is
// a marginal gap around one or two edges of the canvas.
this[$container].style.backgroundColor = backgroundColor;
}

this[$applyEnvironmentMap](environmentMap.texture);
Expand Down
62 changes: 42 additions & 20 deletions src/model-viewer-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export const $progressTracker = Symbol('progressTracker');
export const $getLoaded = Symbol('getLoaded');
export const $getModelIsVisible = Symbol('getModelIsVisible');

interface ToBlobOptions {
mimeType?: string, qualityArgument?: number, idealAspect?: boolean
}

/**
* Definition for a basic <model-viewer> element.
*/
Expand Down Expand Up @@ -332,30 +336,48 @@ export default class ModelViewerElementBase extends UpdatingElement {
}

/** @export */
async toBlob(mimeType?: string, qualityArgument?: number): Promise<Blob> {
return new Promise(async (resolve, reject) => {
if ((this[$canvas] as any).msToBlob) {
// NOTE: msToBlob only returns image/png
// so ensure mimeType is not specified (defaults to image/png)
// or is image/png, otherwise fallback to using toDataURL on IE.
if (!mimeType || mimeType === 'image/png') {
return resolve((this[$canvas] as any).msToBlob());
async toBlob(options?: ToBlobOptions): Promise<Blob> {
const mimeType = options ? options.mimeType : undefined;
const qualityArgument = options ? options.qualityArgument : undefined;
const idealAspect = options ? options.idealAspect : undefined;
const {width, height, model, aspect} = this[$scene];
if (idealAspect === true) {
const idealWidth = model.fieldOfViewAspect > aspect ?
width :
Math.round(height * model.fieldOfViewAspect);
const idealHeight = model.fieldOfViewAspect > aspect ?
Math.round(width / model.fieldOfViewAspect) :
height;
this[$updateSize]({width: idealWidth, height: idealHeight});
await new Promise(resolve => requestAnimationFrame(resolve));
}
try {
return new Promise<Blob>(async (resolve, reject) => {
if ((this[$canvas] as any).msToBlob) {
// NOTE: msToBlob only returns image/png
// so ensure mimeType is not specified (defaults to image/png)
// or is image/png, otherwise fallback to using toDataURL on IE.
if (!mimeType || mimeType === 'image/png') {
return resolve((this[$canvas] as any).msToBlob());
}
}
}

if (!this[$canvas].toBlob) {
return resolve(await dataUrlToBlob(
this[$canvas].toDataURL(mimeType, qualityArgument)));
}

this[$canvas].toBlob((blob) => {
if (!blob) {
return reject(new Error('Unable to retrieve canvas blob'));
if (!this[$canvas].toBlob) {
return resolve(await dataUrlToBlob(
this[$canvas].toDataURL(mimeType, qualityArgument)));
}

resolve(blob);
}, mimeType, qualityArgument);
});
this[$canvas].toBlob((blob) => {
if (!blob) {
return reject(new Error('Unable to retrieve canvas blob'));
}

resolve(blob);
}, mimeType, qualityArgument);
})
} finally {
this[$updateSize]({width, height});
};
}

get[$ariaLabel]() {
Expand Down
7 changes: 5 additions & 2 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,13 @@ canvas.show {
.slot.poster {
opacity: 0;
transition: opacity 0.3s 0.3s;
background-color: inherit;
}
.slot.poster.show {
opacity: 1;
transition: none;
background-color: inherit;
}
.slot.poster > * {
Expand All @@ -107,9 +109,10 @@ canvas.show {
width: 100%;
height: 100%;
position: absolute;
background-size: cover;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-color: var(--poster-color, #fff);
background-color: var(--poster-color, inherit);
background-image: var(--poster-image, none);
}
Expand Down
97 changes: 66 additions & 31 deletions src/test/model-viewer-base-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,30 @@

import {IS_IE11} from '../constants.js';
import ModelViewerElementBase, {$canvas, $renderer, $scene} from '../model-viewer-base.js';
import {Constructor} from '../utilities.js';
import {Constructor, resolveDpr} from '../utilities.js';

import {assetPath, spy, timePasses, until, waitForEvent} from './helpers.js';
import {BasicSpecTemplate} from './templates.js';


const expect = chai.expect;

const expectBlobDimensions =
async (blob: Blob, width: number, height: number) => {
const img = await new Promise<HTMLImageElement>((resolve) => {
const url = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
resolve(img);
};
img.src = url;
});

const dpr = resolveDpr();
expect(img.width).to.be.equal(width * dpr);
expect(img.height).to.be.equal(height * dpr);
};

suite('ModelViewerElementBase', () => {
test('is not registered as a custom element by default', () => {
expect(customElements.get('model-viewer-base')).to.be.equal(undefined);
Expand Down Expand Up @@ -186,7 +202,7 @@ suite('ModelViewerElementBase', () => {

// Avoid testing our memory ceiling in CI by limiting the size
// of the screenshots we produce in these tests:
element.style.width = '64px';
element.style.width = '32px';
element.style.height = '64px';

document.body.appendChild(element);
Expand Down Expand Up @@ -231,7 +247,8 @@ suite('ModelViewerElementBase', () => {
// Emulate unsupported browser
let restoreCanvasToBlob = () => {};
try {
restoreCanvasToBlob = spy(HTMLCanvasElement.prototype, 'toBlob', { value: undefined });
restoreCanvasToBlob =
spy(HTMLCanvasElement.prototype, 'toBlob', {value: undefined});
} catch (error) {
// Ignored...
}
Expand All @@ -242,34 +259,52 @@ suite('ModelViewerElementBase', () => {
restoreCanvasToBlob();
});

test('blobs on supported and unsupported browsers are equivalent', async () => {
// Skip test on IE11 since it doesn't have Response to fetch arrayBuffer
if (IS_IE11) {
return;
}

let restoreCanvasToBlob = () => {};
try {
restoreCanvasToBlob = spy(HTMLCanvasElement.prototype, 'toBlob', { value: undefined });
} catch (error) {
// Ignored...
}

const unsupportedBrowserBlob = await element.toBlob();

restoreCanvasToBlob();

const supportedBrowserBlob = await element.toBlob();

// Blob.prototype.arrayBuffer is not available in Edge / Safari
// Using Response to get arrayBuffer instead
const supportedBrowserResponse = new Response(supportedBrowserBlob);
const unsupportedBrowserResponse = new Response(unsupportedBrowserBlob);

const supportedBrowserArrayBuffer = await supportedBrowserResponse.arrayBuffer();
const unsupportedBrowserArrayBuffer = await unsupportedBrowserResponse.arrayBuffer();

expect(unsupportedBrowserArrayBuffer).to.eql(supportedBrowserArrayBuffer);
test(
'blobs on supported and unsupported browsers are equivalent',
async () => {
// Skip test on IE11 since it doesn't have Response to fetch
// arrayBuffer
if (IS_IE11) {
return;
}

let restoreCanvasToBlob = () => {};
try {
restoreCanvasToBlob = spy(
HTMLCanvasElement.prototype, 'toBlob', {value: undefined});
} catch (error) {
// Ignored...
}

const unsupportedBrowserBlob = await element.toBlob();

restoreCanvasToBlob();

const supportedBrowserBlob = await element.toBlob();

// Blob.prototype.arrayBuffer is not available in Edge / Safari
// Using Response to get arrayBuffer instead
const supportedBrowserResponse =
new Response(supportedBrowserBlob);
const unsupportedBrowserResponse =
new Response(unsupportedBrowserBlob);

const supportedBrowserArrayBuffer =
await supportedBrowserResponse.arrayBuffer();
const unsupportedBrowserArrayBuffer =
await unsupportedBrowserResponse.arrayBuffer();

expect(unsupportedBrowserArrayBuffer)
.to.eql(supportedBrowserArrayBuffer);
});

test('idealAspect gives the proper blob dimensions', async () => {
const basicBlob = await element.toBlob();
const idealBlob = await element.toBlob({idealAspect: true});
const idealHeight =
Math.round(32 / element[$scene].model.fieldOfViewAspect);
await expectBlobDimensions(basicBlob, 32, 64);
await expectBlobDimensions(idealBlob, 32, idealHeight);
});
});
});
Expand Down

0 comments on commit 58db473

Please sign in to comment.