better canvas background handling

This commit is contained in:
dsehnal
2025-12-21 16:15:56 +01:00
parent a7330f40d7
commit ea07cd89de
8 changed files with 51 additions and 36 deletions

View File

@@ -30,6 +30,8 @@ Note that since we don't clearly distinguish between a public and private interf
- Fix `flipSided` for meshes
- Add `label/auth_comp_id` to `StructureProperties.residue`
- Previously, this has been only been present on `.atom` (since residue name can alter on per-atom basis), but this has been a bit confusing for the general use-case
- Move canvas "checked background" logic to `canvas3d.ts` and only apply it when `transparentBackground` is on
- This prevents an ugly flicked during plugin initialization
## [v5.4.2] - 2025-12-07
- Fix postprocessing issues with SSAO and outlines for large structures (#1387)

1
breaking-v6-changes.md Normal file
View File

@@ -0,0 +1 @@
- Remove `checkeredCanvasBackground` from `PluginContext` and `PluginContainer`

View File

@@ -83,6 +83,9 @@ export class Viewer {
}
const spec: PluginUISpec = {
canvas3d: {
...defaultSpec.canvas3d,
},
actions: defaultSpec.actions,
behaviors: [
...baseBehaviors,

View File

@@ -6,12 +6,16 @@
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<title>Mol* Viewer MolViewSpec Example</title>
<style>
body {
background: #111318;
}
#app {
position: absolute;
left: 100px;
top: 100px;
width: 800px;
height: 600px;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#controls {
@@ -23,7 +27,7 @@
left: 10px;
top: 10px;
z-index: 10;
background-color: black;
background-color: #111318;
padding: 10px;
color: white;
}
@@ -96,7 +100,7 @@
})
builder.canvas({
background_color: "black",
background_color: "#111318",
})
structure.primitives()

View File

@@ -96,6 +96,7 @@ export const Canvas3DParams = {
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
transparentBackground: PD.Boolean(false),
checkeredTransparentBackground: PD.Boolean(false),
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
pickPadding: PD.Numeric(3, { min: 0, max: 10, step: 1 }, { description: 'Extra pixels to around target to check in case target is empty.' }),
userInteractionReleaseMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time before the user is not considered interacting anymore.' }),
@@ -405,6 +406,22 @@ const cancelAnimationFrame = typeof window !== 'undefined'
? window.cancelAnimationFrame
: (handle: number) => clearImmediate(handle as unknown as NodeJS.Immediate);
function syncCanvasBackground(canvas: HTMLCanvasElement, canvasProps: Canvas3DProps) {
if (canvasProps.transparentBackground && canvasProps.checkeredTransparentBackground) {
Object.assign(canvas.style, {
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
'background-size': '60px 60px',
'background-position': '0 0, 30px 30px'
});
} else {
Object.assign(canvas.style, {
'background-image': '',
'background-size': '',
'background-position': ''
});
}
}
namespace Canvas3D {
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
@@ -435,6 +452,7 @@ namespace Canvas3D {
let forceNextRender = false;
let currentTime = 0;
syncCanvasBackground(canvas!, p);
updateViewport();
const scene = Scene.create(webgl, passes.draw.transparency, {
dColorMarker: p.renderer.colorMarker,
@@ -1061,6 +1079,7 @@ namespace Canvas3D {
cameraResetDurationMs: p.cameraResetDurationMs,
sceneRadiusFactor: p.sceneRadiusFactor,
transparentBackground: p.transparentBackground,
checkeredTransparentBackground: p.checkeredTransparentBackground,
dpoitIterations: p.dpoitIterations,
pickPadding: p.pickPadding,
userInteractionReleaseMs: p.userInteractionReleaseMs,
@@ -1311,6 +1330,7 @@ namespace Canvas3D {
}
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
if (props.checkeredTransparentBackground !== undefined) p.checkeredTransparentBackground = props.checkeredTransparentBackground;
if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations;
if (props.pickPadding !== undefined) {
p.pickPadding = props.pickPadding;
@@ -1357,6 +1377,12 @@ namespace Canvas3D {
p.camera.stereo.name = 'off';
}
if ('transparentBackground' in props
|| 'checkeredTransparentBackground' in props
|| (props.renderer && 'backgroundColor' in props.renderer)) {
syncCanvasBackground(canvas!, p);
}
shaderManager.updateRequired(p);
if (!doNotRequestDraw) {
requestDraw();

View File

@@ -22,27 +22,6 @@
z-index: 1000;
}
.msp-viewport-host3d {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-touch-callout: none;
touch-action: manipulation;
>canvas {
background-color: $default-background;
background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey),
linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey);
background-size: 60px 60px;
background-position: 0 0, 30px 30px;
}
}
.msp-viewport-controls {
position: absolute;
right: $control-spacing;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2024-25 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -19,6 +19,10 @@ export class PluginContainer {
this.parent.parentElement?.removeChild(this.parent);
}
/**
* options.checkeredCanvasBackground has no effect. Use canvas3d.checkeredTransparentBackground instead.
* TODO: remove in v6
*/
constructor(public options?: { checkeredCanvasBackground?: boolean, canvas?: HTMLCanvasElement }) {
const parent = document.createElement('div');
Object.assign(parent.style, {
@@ -36,13 +40,6 @@ export class PluginContainer {
let canvas = options?.canvas;
if (!canvas) {
canvas = document.createElement('canvas');
if (options?.checkeredCanvasBackground) {
Object.assign(canvas.style, {
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
'background-size': '60px 60px',
'background-position': '0 0, 30px 30px'
});
}
parent.appendChild(canvas);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -246,6 +246,9 @@ export class PluginContext {
if (!this._initViewer(container.canvas, container.parent, options?.canvas3dContext)) {
return false;
}
if (options?.checkeredCanvasBackground) {
this.canvas3d?.setProps({ checkeredTransparentBackground: true });
}
this.container = container;
return true;
}