Open itlackey opened 4 days ago
At present, this is possible by:
For example,
import { Editor, ImageComponent, Mat33, BackgroundComponentBackgroundType, Color4 } from 'js-draw';
class BackgroundImage extends ImageComponent {
// Add copies of the image to the background layer, rather
// than the main layer.
public isBackground() {
return true;
}
// TODO: If using collaborative editing, also override `serialize`
// and register a deserialization callback with `registerComponent`.
}
const editor = new Editor(document.body);
// Create an Image in the format expected by js-draw. Usually,
// these steps are handled by ImageComponent.fromImage. That static method,
// however, only constructs the base ImageComponent class (and not
// subclasses).
const myHtmlImage = new Image();
// js-draw uses the base64 URL when saving to SVG.
myHtmlImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAAKklEQVQIW2Ns022zZGRgfPnz8s8HDQwN/xgZgKBDu0PuL8tf5d8/fz8FAOiDD1H2gfpGAAAAAElFTkSuQmCC';
// Waiting for the image to load -- this ensures that the created
// BackgroundImage will have the correct bounding box.
myHtmlImage.onload = async () => {
// Set the position/rotation/transformation of the background image
const rotated45Degrees = Mat33.zRotation(Math.PI / 4); // A 45 degree = pi/4 radian rotation
const scaledByFactorOf100 = Mat33.scaling2D(100);
// Scale **and** rotate
const transform = rotated45Degrees.rightMul(scaledByFactorOf100);
// Create the background image
const imageComponent = new BackgroundImage({
image: myHtmlImage,
base64Url: myHtmlImage.src,
transform: transform,
});
// Add the background image:
editor.image.addElement(imageComponent).apply(editor);
// Move it behind other components in the background layer:
imageComponent.setZIndex(-1).apply(editor);
// Add the background:
await editor.dispatch(editor.setBackgroundStyle({
color: Color4.ofRGBA(150, 0, 0, 0.24),
type: BackgroundComponentBackgroundType.Grid,
autoresize: true,
}));
}
Explanation:
js-draw
stores components in an EditorImage data structure. It currently always has two layers: a foreground layer and a background layer.
Components that have an isBackground()
method that returns true
are placed in the background layer. Other components are added to the foreground layer. The built-in BackgroundComponent, for example, is added to the background layer by default.
To add ImageComponent
s to the background layer, it's currently necessary to subclass ImageComponent
such that its isBackground()
method returns true
. It can then be placed beneath other background components by giving it a low zIndex
with setZIndex
.
Thanks for the quick and helpful response! I think I am following, but just to make sure I understand, this code should be used to insert all images to the background layer to allow the grid z-index to be higher in the same layer? Would these objects still be able to be selected, moved etc?
-Edit: I just saw the explanation. This makes sense. I will give it a try.
this code should be used to insert all images to the background layer to allow the grid z-index to be higher in the same layer?
That's correct! If the imageComponent.setZIndex(-1).apply(editor)
line is replaced with imageComponent.setZIndex(someLargeNumber).apply(editor)
, then the image component will instead be added on top of the grid.
Would these objects still be able to be selected, moved etc?
No -- currently only components in the foreground layer can be moved/selected/etc.
I need the images to behave as normal images. Would it be easier to move the grid to the foreground layer and disable selection? Is that even possible?
I need the images to behave as normal images. Would it be easier to move the grid to the foreground layer and disable selection? Is that even possible?
I think so — even in the foreground layer, the background component should have selection disabled. For example,
Selecting and moving components brings those components to the top of the image. To prevent this, one option could be to listen for commands through Editor.notifier, then increase the z-index of the grid component.
An alternative might be to create the grid with an HTML element and position it above the editor. Viewport updates (e.g. when the user zooms in/out) can be detected by listening to events from Editor.notifier and used to position the HTML element. This would be similar to how the TextTool draws a text editor over the canvas.
This sounds like a workable solution. I will give it a try and let you know how it goes.
Just as a little more context, what I am trying to build is something like this: https://www.owlbear.rodeo/
What you have built is fantastic and solves most of what is needed to make something like this. I already have it wired to socket.io to do real time updates, and am currently working through "host" vs player toolbars / actions.
I had started on building this from scratch, but after finding your project and starting to refactor to use js-draw as the foundation of the shared "table". I hope to update this repo (https://github.com/dimm-city/dimm-city-portal) once I have a few more things worked out.
Any guidance on how to (or not to) build something like this is much appreciated.
Thanks again for all your help and hard work!
I am still working through some unrelated migration tasks, but this appears to be working well! Here is what I have for the GridComponent. When/if you get a chance, please let me know if you see any glaring issues with my implementation.
I am looking for a way to show the grid on top of the background image.
It would be great if it was possible to style the background grid and adjust if it displays over or under other objects on the canvas. The bounding box already does this selectively. Ideally the background grid would have a way to do the same.
I have tried writing CSS rules to override the default style and/or bring the grid lines to the top.
This feature is ideal for using js-draw as a virtual table top with a map and a grid overlay. I could see it also being useful while drawing to see the grid overlay your pen strokes etc.
I am happy to contribute code to enable this feature, but will need some direction. All of my attempts so far have not been successful.