Today I was asked to add the ability to place a custom logo onto an instance of the Forge viewer (in my case for Dasher 360, of course). It seemed like an interesting one to share, as I’m sure others have the same requirement.
There are probably lots of ways to solve this – for instance by adding the image with its own camera as an overlay inside the Forge viewer’s 3D scene – but I decided to stick to something simple and have the browser overlay the image.
There are a few changes needed for this to work. Firstly to the HTML page hosting the viewer. Here’s the div structure I used, based on this example.
<div id="canvas-align">
<div id="viewer3d"></div>
<div id="logo-overlay">
<div id="logo"></div>
</div>
</div>
Then some CSS additions:
#canvas-align {
position: relative;
width: 100%;
height: calc(100vh - 50px);
margin: auto;
}
#logo-overlay {
position: absolute;
top: 0;
width: 100%;
height: calc(100vh - 50px);
margin: auto;
z-index: 1;
pointer-events: none;
}
#logo {
position: absolute;
bottom: 20px;
right: 20px;
}
In the above implementation we assume a header that’s 50 pixels high, hence the height adjustment. And we’re placing the logo 20 pixels up and in from the bottom right-hand corner of the viewer. Another important thing to note is that we need to flag our overlay layer as not accepting pointer events. Otherwise you wouldn’t be able to use the viewer, as it would swallow all the mouse or touch input.
Lastly there’s the JavaScript – or in our case TypeScript – that makes it all work. The below code won’t copy & paste directly – there’s a Logger class that I haven’t provided, for instance – but it should help you implement this approach in your own projects. I’ve implemented this in an extension – to make it easier to make it loadable/unloadable, should you choose to – but you could bypass that mechanism completely if you wanted to.
/// <reference path='../../../../../typings/lmv-client/lmv-client.d.ts' />
import Logger from '../../core/logger';
let logger = Logger.getInstance();
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func: any, wait: any, immediate?: any): any {
let timeout: any;
return function(): any {
let context = this;
let args = arguments;
let later = function(): any {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
};
export default class LogoExtension extends Autodesk.Viewing.Extension {
private _overlayName = 'Logo';
private _logo: THREE.Mesh;
constructor(viewer: Autodesk.Viewing.Private.GuiViewer3D, options: any) {
super(viewer, options);
}
adjustLogo(): void {
let vdiv = $('.adsk-viewing-viewer')[0];
$('#canvas-align').width(vdiv.clientWidth).height(vdiv.clientHeight);
$('#logo-overlay').width(vdiv.clientWidth).height(vdiv.clientHeight);
}
load(): boolean {
logger.log('LogoExtension loaded');
let onresize = debounce(() => {
this.adjustLogo();
}, 100);
window.addEventListener('resize', onresize);
let logo = $('#logo');
let width = 80;
let height = 80;
logo.html('<img width=\'' + width + 'px\' height=\'' + height + 'px\' src=\'/img/DasherIcon.png\'>');
this.adjustLogo();
$('#logo-overlay').attr('display', 'block');
return true;
}
unload(): boolean {
logger.log('LogoExtension unloaded');
$('#logo-overlay').attr('display', 'none');
return true;
}
}
It’s important to note that in the above code we’re adjusting the logo to the viewer’s size, rather than to the size of the window. That’s because we sometimes add our timeline to the bottom of the screen, and we don’t want the logo overlaying that. And we do this from a “debounced” function, to stop it from being called repeatedly during resize, adversely impacting performance.
Here’s how Dasher 360 looks with an overlaid logo in the bottom-right corner. This is just a placeholder logo – it’ll be something that gets customized per installation, in due course – but I’m pretty happy with how it looks and behaves.