<div id="canvasLeftId">
<h3>Three js Canvas</h3>
<button id="drawing-mode" class="btn btn-info">Cancel drawing mode</button>
<div id="canvasForDrawingThreejsScene_Id"></div>
</div>
<div id="canvasRightId">
<h3>Fabric js Canvas</h3>
<canvas id="canvasForDrawingFabricjs_Id" width="256" height="256"></canvas>
</div>
<div id="div2Id">
<h3>Hidden Canvas for Fabric js lower and Upper Canvases</h3>
<canvas id="offscreenCanvasForDrawingTheFabricjsUpperAndLowerCanvas_Id" width="256" height="256"></canvas>
</div>
html, body {
width: 100%;
height: 100%;
}
body {
background: #FFF7F4;
}
#canvasLeftId {
position: relative;
display: inline-block;
height: 100%;
width: 50%;
}
#canvasRightId {
float: right;
position: relative;
display: inline-block;
height: 100%;
width: 50%;
}
#canvasForDrawingThreejsScene_Id {
position: relative;
height: 256px;
width: 256px;
}
#canvasForDrawingFabricjs_Id {
height: 100%;
width: 100%;
}
#div2Id {
display: none;
}
/**
* Fabric.js
*/
// https://stackoverflow.com/questions/60350747/fabric-js-patterns-how-create-pattern-for-canvas-background-from-rects-without
console.log('fabric.devicePixelRatio', fabric.devicePixelRatio);
// fabric.devicePixelRatio = Math.max(Math.floor(fabric.devicePixelRatio), 1);
fabric.devicePixelRatio = 1;
console.log('fabric.devicePixelRatio2', fabric.devicePixelRatio);
var fabricCanvas = new fabric.Canvas('canvasForDrawingFabricjs_Id');
fabricCanvas.backgroundColor = '#FFBE9F';
fabricCanvas.freeDrawingBrush = new fabric['PencilBrush'](fabricCanvas);
fabricCanvas.isDrawingMode = true;
fabricCanvas.setDimensions({width:256, height:256});
console.log('fabricCanvas.lowerCanvasEl.width', fabricCanvas.lowerCanvasEl.width);
console.log('fabricCanvas.upperCanvasEl.width', fabricCanvas.upperCanvasEl.width);
console.log('fabricCanvas.upperCanvasEl.height', fabricCanvas.upperCanvasEl.height);
var rectangle = new fabric.Rect({
top: 100,
left: 100,
fill: '#FF6E27',
width: 100,
height: 100,
transparentCorners: false,
centeredScaling: true,
});
fabricCanvas.add(rectangle);
/**
* Threejs
*/
var camera, renderer, canvasForDrawingThreejsSceneEl, scene, texture, material, geometry, cube;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var clickedPointCoords = new THREE.Vector2();
init();
animate();
/**
* Configurator init function
*/
var drawingModeEl = document.getElementById('drawing-mode');
drawingModeEl.onclick = function() {
fabricCanvas.isDrawingMode = !fabricCanvas.isDrawingMode;
if (fabricCanvas.isDrawingMode) {
drawingModeEl.innerHTML = 'Cancel drawing mode';
}
else {
drawingModeEl.innerHTML = 'Enter drawing mode';
}
};
function init() {
/**
* Camera
*/
camera = new THREE.PerspectiveCamera(
30,
window.innerWidth / window.innerHeight,
0.01,
100
);
camera.position.set(0, 0, 3.5);
/**
* Canvas0
*/
canvasForDrawingThreejsSceneEl = document.getElementById('canvasForDrawingThreejsScene_Id');
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(canvasForDrawingThreejsSceneEl.clientWidth, canvasForDrawingThreejsSceneEl.clientHeight);
camera.aspect = canvasForDrawingThreejsSceneEl.clientWidth / canvasForDrawingThreejsSceneEl.clientHeight;
camera.updateProjectionMatrix();
canvasForDrawingThreejsSceneEl.appendChild(renderer.domElement);
/**
* Scene
*/
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
/**
* Texture and material
*/
texture = new THREE.Texture(document.getElementById('canvasForDrawingFabricjs_Id'));
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
material = new THREE.MeshBasicMaterial({ map: texture });
/**
* Model
*/
geometry = new THREE.BoxGeometry(1, 1, 1);
cube = new THREE.Mesh(geometry, material);
cube.rotation.x = 0.5;
cube.rotation.y = 0.3;
scene.add(cube);
}
/**
* Configurator frame render function
*/
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.004;
cube.rotation.y += 0.001;
// cube.rotation.x += 0.000;
// cube.rotation.y += 0.000;
texture.needsUpdate = true;
// console.log('scene.children[0].material.map.image', scene.children[0].material.map.image);
renderer.render(scene, camera);
}
/**
* Fabric.js patch
*/
fabric.Canvas.prototype.getPointer = function (e, ignoreZoom) {
if (this._absolutePointer && !ignoreZoom) {
return this._absolutePointer;
}
if (this._pointer && ignoreZoom) {
return this._pointer;
}
var pointer = fabric.util.getPointer(e),
upperCanvasEl = this.upperCanvasEl,
bounds = upperCanvasEl.getBoundingClientRect(),
boundsWidth = bounds.width || 0,
boundsHeight = bounds.height || 0,
cssScale;
if (!boundsWidth || !boundsHeight ) {
if ('top' in bounds && 'bottom' in bounds) {
boundsHeight = Math.abs( bounds.top - bounds.bottom );
}
if ('right' in bounds && 'left' in bounds) {
boundsWidth = Math.abs( bounds.right - bounds.left );
}
}
this.calcOffset();
pointer.x = pointer.x - this._offset.left;
pointer.y = pointer.y - this._offset.top;
/* BEGIN PATCH CODE */
if (e.target !== this.upperCanvasEl) {
var positionOnScene = getPositionOnScene(canvasForDrawingThreejsSceneEl, e);
// console.log('positionOnScene:', positionOnScene);
pointer.x = positionOnScene.x;
pointer.y = positionOnScene.y;
// console.log('pointer', pointer);
}
/* END PATCH CODE */
// console.log('pointer1:', pointer);
if (!ignoreZoom) {
pointer = this.restorePointerVpt(pointer);
}
if (boundsWidth === 0 || boundsHeight === 0) {
// console.log('bar2');
cssScale = { width: 1, height: 1 };
}
else {
// console.log('bar3');
cssScale = {
width: upperCanvasEl.width / boundsWidth,
height: upperCanvasEl.height / boundsHeight
};
}
return {
x: pointer.x * cssScale.width,
y: pointer.y * cssScale.height
};
};
fabric.Object.prototype._drawControl = function(control, ctx, methodName, left, top, styleOverride) {
// console.log('bar1');
// console.log('control', control);
styleOverride = styleOverride || {};
if (!this.isControlVisible(control)) {
return;
}
// console.log('control', control);
// console.log('bar2');
var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor;
switch (styleOverride.cornerStyle || this.cornerStyle) {
case 'rect':
if(control == this.__corner){
ctx.save();
// console.log('set control to red');
ctx.strokeStyle = ctx.fillStyle='red';
}
else {
// console.log('set control to black');
ctx.strokeStyle = ctx.fillStyle='black';
}
ctx.beginPath();
ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
ctx[methodName]();
if (stroke) {
ctx.stroke();
}
if(control == this.__corner){
ctx.restore();
}
break;
default:
this.transparentCorners || ctx.clearRect(left, top, size, size);
ctx[methodName + 'Rect'](left, top, size, size);
if (stroke) {
ctx.strokeRect(left, top, size, size);
}
}
};
/**
* Listeners
*/
canvasForDrawingThreejsSceneEl.addEventListener('mousedown', onMouseDown111, false);
canvasForDrawingThreejsSceneEl.addEventListener('touchstart', onTouchStart111, false);
var offscreenCanvasEl;
// cRightEl = document.getElementById('canvasRightId');
let cRightEl = document;
cRightEl.addEventListener('mousedown', onMouseDown222, true);
cRightEl.addEventListener('touchstart', onTouchStart222, false);
function drawOffscreen(){
var upperCanvas1 = fabricCanvas.upperCanvasEl;
var lowerCanvas1 = fabricCanvas.lowerCanvasEl;
// grab the context from your destination canvas
var destCtx = offscreenCanvasEl.getContext('2d');
// call its drawImage() function passing it the source canvas directly
destCtx.drawImage(lowerCanvas1, 0, 0);
destCtx.drawImage(upperCanvas1, 0, 0);
}
// changes texture pointer
function onMouseDown222(ev){
cRightEl.addEventListener('mouseup', onMouseUp222, true);
if(fabricCanvas.isDrawingMode){
cRightEl.addEventListener('mousemove', onMouseMove222, true);
offscreenCanvasEl = document.getElementById('offscreenCanvasForDrawingTheFabricjsUpperAndLowerCanvas_Id');
offscreenCanvasEl.width = fabricCanvas.width;
offscreenCanvasEl.height = fabricCanvas.height;
// console.log('offscreenCanvasEl.width2', offscreenCanvasEl.width);
material.map.image = offscreenCanvasEl;
}
}
function onMouseMove222(ev){
if(fabricCanvas.isDrawingMode){
drawOffscreen();
}
}
// returns original canvas pointer
function onMouseUp222(ev){
cRightEl.removeEventListener('mouseup', onMouseUp222, true);
if(fabricCanvas.isDrawingMode){
let gg = document.getElementById('canvasForDrawingFabricjs_Id');
material.map.image = gg;
cRightEl.removeEventListener('mousemove', onMouseMove222, true);
}
}
function onTouchStart222(ev){
cRightEl.addEventListener('touchend', onTouchEnd222, true);
if(fabricCanvas.isDrawingMode){
cRightEl.addEventListener('touchmove', onTouchMove222, true);
offscreenCanvasEl = document.getElementById('offscreenCanvasForDrawingTheFabricjsUpperAndLowerCanvas_Id');
offscreenCanvasEl.width = fabricCanvas.width;
offscreenCanvasEl.height = fabricCanvas.height;
console.log('offscreenCanvasEl.width2', offscreenCanvasEl.width);
material.map.image = offscreenCanvasEl;
}
}
function onTouchMove222(ev){
if(fabricCanvas.isDrawingMode){
drawOffscreen();
}
}
function onTouchEnd222(ev){
cRightEl.removeEventListener('touchend', onTouchEnd222, true);
if(fabricCanvas.isDrawingMode){
let gg = document.getElementById('canvasForDrawingFabricjs_Id');
material.map.image = gg;
cRightEl.removeEventListener('touchmove', onTouchMove222, true);
}
}
/**
* Event handler
*/
function onMouseDown111(evt) {
evt.preventDefault();
const positionOnScene = getPositionOnScene(canvasForDrawingThreejsSceneEl, evt);
if (positionOnScene) {
const canvasRect = fabricCanvas._offset;
const simEvt = new MouseEvent(evt.type, {
clientX: canvasRect.left + positionOnScene.x,
clientY: canvasRect.top + positionOnScene.y
});
fabricCanvas.upperCanvasEl.dispatchEvent(simEvt);
}
}
function onTouchStart111(evt) {
evt.preventDefault();
const positionOnScene = getPositionOnScene(canvasForDrawingThreejsSceneEl, evt);
if (positionOnScene) {
const canvasRect = fabricCanvas._offset;
const simEvt = new TouchEvent(evt.type, {
clientX: canvasRect.left + positionOnScene.x,
clientY: canvasRect.top + positionOnScene.y
});
fabricCanvas.upperCanvasEl.dispatchEvent(simEvt);
}
}
/**
* Three.js Helper functions
*/
function getPositionOnScene(sceneContainer, evt) {
// console.log('evt', evt);
let clientX = 0;
let clientY = 0;
if(evt instanceof MouseEvent) {
clientX = evt.clientX;
clientY = evt.clientY;
}
else if(evt instanceof TouchEvent) {
clientX = event.changedTouches[0].clientX;
clientY = event.changedTouches[0].clientY;
}
else{
throw new Error('event type is not supported');
}
// console.log('clientX', clientX);
// console.log('clientY', clientY);
var array = getMousePosition(canvasForDrawingThreejsSceneEl, clientX, clientY);
clickedPointCoords.fromArray(array);
// console.log('clickedPointCoords', clickedPointCoords);
var intersects = getIntersects(clickedPointCoords, scene.children);
if (intersects.length > 0 && intersects[0].uv) {
var uv = intersects[0].uv;
// console.log('intersects[0].uv: ', intersects[0].uv);
// console.log('uv: ', uv);
intersects[0].object.material.map.transformUv(uv);
// console.log('uv.x', uv.x);
// console.log('uv.y', uv.y);
let x1 = getRealPosition('x', uv.x);
let y1 = getRealPosition('y', uv.y);
// console.log('x1', x1);
// console.log('y1', y1);
// let x2 = uv.x * canvasForDrawingThreejsSceneEl.clientHeight;
// let y2 = uv.y * canvasForDrawingThreejsSceneEl.clientWidth;
// console.log('x2', x2);
// console.log('y2', y2);
return {
x: x1,
y: y1
};
}
return null;
}
function getRealPosition(axis, value) {
let CORRECTION_VALUE = axis === 'x' ? 4.5 : 5.5;
return Math.round(value * canvasForDrawingThreejsSceneEl.clientHeight) - CORRECTION_VALUE;
}
var getMousePosition = function(dom, x, y) {
var rect = dom.getBoundingClientRect();
// console.log('rect', rect);
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
};
var getIntersects = function(point, objects) {
mouse.set(point.x * 2 - 1, -(point.y * 2) + 1);
raycaster.setFromCamera(mouse, camera);
return raycaster.intersectObjects(objects);
};
This Pen doesn't use any external CSS resources.