how to fix this Error Cannot use the same canvas during multiple render() operations - reactjs

I am using canvas in react and rendering pdf's as images using the canvas.
Now, when I get new data i.e another pdf get's added, then
again have to use the canvases for that.
I am not sure how to fix this error or how to remove the canvas or even clear the canvas before using it again.
Here's the relevant code:
pdfLoop = (item,index) => {
var that = this;
PDFJS.getDocument(item).then(function getPdfHelloWorld(pdf) {
//
// Fetch the first page
console.log('url is : ',item);
pdf.getPage(1).then(function getPageHelloWorld(page) {
var scale = 0.5;
var viewport = page.getViewport(scale);
let cref = 'canvas'+index;
let imgref ='img'+index;
console.log('cref no : ',cref);
console.log('img no : ',imgref);
// Prepare canvas using PDF page dimensions
//
var canvas = that.canvasRefs[cref];
//let imagez = that.imageRefs[imgref];
var context = canvas.getContext('2d');
context.globalcompositeoperation = 'source-over';
// context.fillStyle = "#fff";
//draw on entire canvas
//context.fillRect( 0, 0, canvas.width, canvas.height );
canvas.height = viewport.height;
canvas.width = viewport.width;
//imagez.src = canvas.toDataURL("image/png");
//
// Render PDF page into canvas context
//
//page.render({canvasContext: context, viewport: viewport});
var task = page.render({canvasContext: context, viewport: viewport})
task.promise.then(function(){
//console.log(canvas.toDataURL('image/png'));
let imgItem = {imgref:canvas.toDataURL('image/png'),page:index+1,rotate:0}
let newState = that.state.imgsrc;
newState[index] = imgItem;
//let newState = that.state.imgsrc.concat(imgItem);
that.setState({
imgsrc:newState
});
//imagez.src = canvas.toDataURL('image/png')
});
});
});
}

In case someone stumbles across this, the error message states the following Use different canvas or ensure previous operations were cancelled or completed.
When getting the document, if there already is a document, it has to be destroyed. I.e:
PDFJS.getDocument({ url: pdf_url }).promise
.then((pdf_doc) => {
if (this.pdf_doc) {
this.pdf_doc.destroy();
}
this.pdf_doc = pdf_doc;
this.total_pages = this.pdf_doc.numPages;
})
I have no idea if this is a good solution, but at least it worked for me.

I've had the same exact problem as you, but my solution was a bit different than the answers previously posted.
For me the problem was the fact that I was unnecessarily re-rendering with a state change. Check if you aren't re-rendering the component without properly clearing the canvas, or if you even need to re-render at all (in my case I didn't).
Hopefully this could help

In order to avoid this situation, put your canvas object in a div object as is shown below:
<div id="div_canvas">
<canvas id="cnv"></canvas>
</div>
Then, before to call pdf.js functions, remove the "div_canvas" content and recreate it:
$("#div_canvas").html("");
$("#div_canvas").html("<canvas id='cnv'></canvas>");

I had exactly the same issue when working with PDF.js, the solution to this is,
You will have to clear your context after the render completes.
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
}
This will make sure that the context is cleared and ready for the next render and the error disappears.
this worked for me.

In some cases, this error will be displayed when the pdf has action buttons such as next/previous or scaling.
In this cases, often you have a function for rendering pdf page such as:
renderPage(num) {
// Using promise to fetch the page
pdfDoc.getPage(num).then(function (page) {
const viewport = page.getViewport({scale: scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
page.render(renderContext);
});
}
To fix the error just perform following changes:
Add two variable for controlling conflict and cache waiting page number:
pageRendering = false; // Check conflict
pageNumPending = null; // Cache waiting page number
Use these variables as follow:
renderPage(num) {
if (this.pageRendering) { // Check if other page is rendering
this.pageNumPending = num; // Cache waited page number until previous page rendering completed
} else {
this.pageRendering = true;
// Using promise to fetch the page
pdfDoc.getPage(num).then(function (page) {
const viewport = page.getViewport({scale: scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
const renderTask = page.render(renderContext);
// Wait for rendering to finish
renderTask.promise.then(function () {
this.pageRendering = false;
if (pageNumPending !== null) {
// Waited page must be rendered
this.renderPage(pageNumPending);
// Must be set to null to prevent infinite loop
this.pageNumPending = null;
}
});
});

One possible cause: React Strict mode renders twice

Related

React google maps API how to make the marker icon fixed size

I've a google map with some markers, but when I zoom in and out the marker icon size changes, is there any way to make that size fixed?
const [markers,setMarkers] = useState([])
function generaMarker(elementolo,index){
var mark = <Marker draggable={modify===true?true:false} onDragEnd={(e)=>modify===true?handleSpostamentoMarker(e,index):null} onClick={(e)=>eliminate===true?deleteMarker(e,index):handleClickEl(elementolo)} label={elementolo.Nome} title={elementolo.Nome} icon={{url:getIcon(elementolo),s : new window.google.maps.Size(50,50),labelOrigin: new window.google.maps.Point(25,60)
}} position={new window.google.maps.LatLng(elementolo.Posizione.Lat,elementolo.Posizione.Lng)}/>
var nonDarmele = markers;
nonDarmele.push(mark);
setMarkers(nonDarmele)
return mark;
}
I use this function to render the markers and memorize them in the arrsy
if I write setMarkers((prev)=>[...prev,mark]) it crashes for the re renders even if the markers array is not rendered
I've tried also using a listener to the map zoom changes for setting new Marker icon sizes but I get that setIcon() method doesen't exists
function handleMarkerSize(map){
var zoom = map.getZoom()
var markerWidth = (zoom/9)*20;
var markerHeight = (zoom/9)*34;
//scaledSize: new window.google.maps.Size(50,50)
markers.forEach((mark)=>{
console.log("ooo",mark)
mark.props.icon = {
url: mark.props.icon.url,
scaledSize: new window.google.maps.Size(markerWidth,markerHeight),
labelOrigin: new window.google.maps.Point(25,60)
}
})
}
I'm using #react-google-maps/api
The map and markers visualization works fine, after rendering all the Markers there is no re render (only if in the function generaMarker() I use setMarkers with prev, and that make it crash)
Thanks for your help

Canvas onclick event react

Hello and thank you for reading this question.
I'm struggling to deal with the onclick event on canvas with React.
I am currently building a component that has to draw bounding boxes on an image and make those boxes fire a onclick event with its coordinates and/or metadata.
I am using the following react-bounding-box component (https://www.npmjs.com/package/react-bounding-box) with a bit of customization.
The idea is that my component receive the following data :
an image
a JSON with a list of items that contains coordinates of bounding boxes and other data related to those boxes.
Once the JSON is loaded, my code iterates on the list of items and draws the bounding boxes on the image using canvas.
My component definition looks like that (I omitted useless lines of code) :
[...]
import BoundingBox from 'react-bounding-box'
[...]
export const ComicImageDrawer = (props) => {
const [boundingBoxesItems, setBoundingBoxesItems] = useState(Array<any>());
const [selectedBoxItem, setSelectedBoxItem] = useState({})
const [selectedBoxIndex, setSelectedBoxIndex] = useState<Number>(-1);
const [currentImageBoxes, setCurrentImageBoxes] = useState(Array<any>())
useEffect(() => {
[...] // loading data
}, [])
// That function is fired when a box is hovered
// param value is the index of the box
// I would like to do the same but with the `onclick` event
function onOver(param) {
[...] // don't care
}
const params = {
[...] // don't care
}
};
return (
<BoundingBox
image={currentImage}
boxes={currentImageBoxes}
options={params.options}
onSelected={onOver}
drawBox={drawBoxCustom}
drawLabel={() => {}}
/>
)
}
The redefined the component drawBox() function to add some customization. So that function definition looks like this :
function drawBoxCustom(canvas, box, color, lineWidth) {
if(!box || typeof box === 'undefined')
return null;
const ctx = canvas.getContext('2d');
const coord = box.coord ? box.coord : box;
let [x, y, width, height] = [0, 0, 0, 0]
[...] // removed useless lines of codes
ctx.lineWidth = lineWidth;
ctx.beginPath();
[...] // drawing element definition
ctx.stroke();
};
I haved tried the following stuff to make the canvas fire an onclick event but it never fires (i also tried other event like mouseover) :
// Listen for mouse moves
canvas.addEventListener('onmouseover', function (event) {
console.log('click event', event);
});
What I would like to obtain is to fire a function in my React component that looks like that. The idea is to determine which box has been clicked :
const handleCanvasClick = (event, box) => {
console.log('event', event);
console.log('box', box);
}
Any help or suggestion would be appreciated.
Thanks.

Angular $scope.$apply exceptions when maximing a Malhar widget

I am working in the Malhar widget framework, which is based on jQuery sortable widgets. ex/ https://github.com/DataTorrent/malhar-angular-dashboard
I am working on some DOM manipulation on each widget (maximize/minimize/refresh), and running into some Angular $scope.$apply exceptions below.
Function details:
The $scope.grabSouthResizer function (working fine) is the Mahlar function that came with the framework; I just modified it slight to also refresh the Kendo UI charts.
The $scope.maxResizer function is my custom function, which is throwing $rootScope:inprog exceptions every time is hits my $scope.$apply();.
$scope.grabSouthResizer = function (e) {
var widgetElm = $element.find('.widget');
e.stopPropagation();
e.originalEvent.preventDefault();
// get the starting horizontal position
// .. code ommitted for brevity
// sets new widget width on mouseup
var mouseup = function (e) {
// calculate height change
var curY = e.clientY;
var pixelChange = curY - initY;
var widgetContainer = widgetElm.find('.widget-content');
var diff = pixelChange;
var height = parseInt(widgetContainer.css('height'), 10);
var newHeight = (height + diff);
$scope.widget.setHeight(newHeight + 'px');
$scope.$emit('widgetChanged', $scope.widget);
$scope.$apply(); // *** NO EXCEPTIONS THROWN ***
$scope.$broadcast('widgetResized', {
height: newHeight
});
// kendo chart - refresh height
var chart = widgetElm.find('.k-chart').data("kendoChart");
if (chart != undefined) {
chart.setOptions({ chartArea: { height: newHeight - (newHeight * .10) } });
chart.resize($(".k-chart"));
}
};
};
$scope.maxResizer = function (e) {
// TODO: properly restore the window to original position..
var widgetElm = $element.find('.widget');
e.stopPropagation(); // testing - same as grabSouthResizer() below
e.originalEvent.preventDefault();
var pixelHeight = widgetElm.height();
var pixelWidth = widgetElm.width();
// fyi: '.k-tree' will auto-resize, so no need to find that
var chart = widgetElm.find('.k-chart').data("kendoChart");
var treelist = widgetElm.find('.k-treelist').data("kendoTreeList");
// height differential (reduce height of container if inner widget is a treelist)
var ht_diff = (chart != undefined ? 200 : 600);
var newHeight = window.innerHeight - ht_diff;
if (!widget.maximized) {
// widget container maximize
widget.maximized = true;
$scope.widget.setWidth(window.innerWidth);
$scope.widget.setHeight(newHeight); //window.innerHeight - ht_diff);
$scope.$emit('widgetChanged', widget);
$scope.$apply(); // *** THROWS $rootScope:inprog EXCEPTIONS !!! ***
$scope.$broadcast('widgetResized', {
width: window.innerWidth,
height: newHeight
});
if (chart != undefined) {
// refresh Kendo chart
chart.setOptions({ chartArea: { height: widgetElm.height()*.9, width: widgetElm.width()*.95 } });
chart.resize($(".k-chart"));
}
}
kendoRefreshTimer(); // this work-around used instead of $scope.$apply()
}
var timer;
function kendoRefreshTimer() {
timer = $timeout(function () {
refreshKendo();
}, 1);
}
function refreshKendo() {
// Kendo chart refresh here...
}
Big question: why is $scope.$apply(); causing errors in my maxResizer function, but not in the Malhar original grabSouthResizer function ? I also understand that $scope.$apply() is NOT recommended, but it seems to be widely used as a work-around.
I would create an online plunk, but I still haven't set up this Malhar widget framework online as of yet. It's a bit complicated to set up.
Your advice is appreciated.
regards,
Bob
* UPDATE *
I updated my post to show how I've worked around this scope.apply issue by using a $timeout function, but I don't like the split-second delay in the UI. i.e. You can see the Kendo chart resizing itself, so it doesn't look so smooth.

pdfjs: How to create more than one canvas to show all the pages?

I am following the approach of
here. The problem is that all the pages are in first canvas. This is because I have only one canvas and I am not sure how I can generate more canvas one after other?
function handlePages(page)
{
var viewport = page.getViewport(canvas.width / page.getViewport(1.0).width);
var ctx = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({ canvasContext: ctx, viewport: viewport });
//Add it to the web page
div.appendChild( canvas);
//Move to next page
currPage++;
if ( $scope.pdfDoc !== null && currPage <= numPages )
{
$scope.pdfDoc.getPage( currPage ).then( handlePages );
}
}
you cut out part of the code from the linked answer that you took this from. notice the document.createElement( "canvas" ). That creates a new canvas for every page.

Drupal.attachBehaviours with jQuery infinitescroll and jQuery masonry

I am a little desperate here. I have been reading everything I was able to find on Drupal.behaviours but obviously its still not enough. I try running a masonry grid with the infinitescroll plugin to attach the new images to the masonry. This works fine so far. The next thing I wanted to implement to my website is a hover effect (which shows information on the images) and later fancybox to show the images in a huger size.
(function ($) {
Drupal.behaviors.views_fluidgrid = {
attach: function (context) {
$('.views-fluidgrid-wrapper:not(.views-fluidgrid-processed)', context).addClass('views-fluidgrid-processed').each(function () {
// hide items while loading
var $this = $(this).css({opacity: 0}),
id = $(this).attr('id'),
settings = Drupal.settings.viewsFluidGrid[id];
$this.imagesLoaded(function() {
// show items after .imagesLoaded()
$this.animate({opacity: 1});
$this.masonry({
//the masonry settings
});
});
//implement the function of jquery.infinitescroll.min.js
$this.infinitescroll({
//the infinitescroll settings
},
//show new items and attach behaviours in callback
function(newElems) {
var newItems = $(newElems).css({opacity: 0});
$(newItems).imagesLoaded(function() {
$(newItems).animate({opacity: 1});
$this.masonry('appended', newItems);
Drupal.attachBehaviours(newItems);
});
});
});
}
};
})(jQuery);
Now I read that I need to Reattach the Drupal.behaviours if I want the hover event to also take place on the newly added content.
(function ($) {
Drupal.behaviors.imgOverlay = {
attach: function (context) {
var timeout;
$('.img_gallery').hover(function() {
$this = $(this);
timeout = setTimeout(change_opacity, 500);
}, reset_opacity);
function change_opacity() {
//set opacity to show the desired elements
}
function reset_opacity() {
clearTimeout(timeout);
//reset opacity to 0 on desired elements
}
}
};
})(jQuery)
Where do I now write the Drupal.attachBehaviours() to make it work actually? Or is there some other error I just dont see atm? I hope I wrote the question so that its understandable and maybe it also helps somebody else, since I experienced that there is no real "official" running Version of this combination in drupal 7.
Ok, the solution is actually pretty simple. When writing it correctly than it also runs. its of course not Drupal.attachBehaviours() but Drupal.attachBehaviors() . So this combination now works and I am finally relieved :).

Resources