html2canvas is working in IE, not working in google chrome - css

I'm trying to export the html part as a pdf file using html2canvas and jsPDF libraries. However, this functionality is working fine in IE and the contents that are available in the window scope is available in the exported pdf where the content inside the scroll bar is not available in chrome. The part has multiple rows where each row is iterated using angularjs ng-repeat and each row has customized css part. Each row should be exported with the applied css and the dynamic data that is available in the screen. Posting the codefor your reference,
Chrome Image
IE Image
Script Code:
$scope.exportFunctionViewData = function(){
html2canvas(document.getElementById('functionViewExport') , {
onrendered: function (canvas) {
var content = canvas.toDataURL('image/jpeg');
var imgWidth = 210;
var pageHeight = 295;
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var doc = new jsPDF('p', 'mm');
var position = 0;
doc.addImage(content, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
doc.addPage();
doc.addImage(content, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.save($scope.title + '-FunctionView.pdf');
}
});
};

Related

Variable font weight on HTML canvas capped at 999?

I'm working on an variable type test for a project with a variable font i created.
The 'wght' axis contains a range of between 0-6600.
I had no problems animating the full range of values above as a DOM element with HTML and CSS.
But while trying out the same thing on HTML canvas, i realised the font weight resets itself to 0 once its passes 1000.
Is there a way to get around this? I tried looking into P5.js but the examples i found still manipulates DOM elements instead of using a canvas.
An excerpt of my code below, but i cant quite share the working demo with the font as the font is meant to be a proprietary font eventually.
#font-face {
font-family: 'TestFont';
src: url('/assets/TestFont_v2.ttf') format('truetype-variations');
font-weight: 0 6600;
}
<canvas id="canvas-text" width="1000" height="500"> </canvas>
/*** js***/
var c = document.getElementById('canvas-text');
var ctx = c.getContext('2d');
function drawCanvas() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = 'white';
ctx.font = (scroll_percentage * 6600) + ' ' + '20px TestFont';
const string_ = 'Lorem ipsum';
ctx.fillText(string_, 20, 20);
}
let scroll_percentage = 0
function updateScrollProgress() {
// get percentage of total scroll
var winScroll = document.body.scrollTop || document.documentElement.scrollTop;
var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
scroll_percentage = winScroll / height;
// update canvas on scroll
drawCanvas()
}

$timeout in a directive link function

So I'm using this directive to collapse a variable height card in Ionic. The directive grabs the auto height and changes it to a defined height so it can then be collapsed to 0 with a css animation. It was working fine for my needs, but now I need to use ng-src to dynamically load an image within the card. What's happening is the image is being loaded after the directive, so the image loads and overflows the card.
Directive:
.directive('collapse', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function ($scope, ngElement, attributes) {
var element = ngElement[0];
$timeout(function(){
$scope.$watch(attributes.collapse, function (collapse) {
var newHeight = collapse ? 0 : getElementAutoHeight();
element.style.height = newHeight +"px";
ngElement.toggleClass('collapsed', collapse);
});
function getElementAutoHeight() {
var currentHeight = getElementCurrentHeight();
element.style.height = 'auto';
var autoHeight = getElementCurrentHeight();
element.style.height = currentHeight +"px";
getElementCurrentHeight(); // Force the browser to recalc height after moving it back to normal
return autoHeight;
}
function getElementCurrentHeight() {
return element.offsetHeight
}
});
}
};
}])
and HTML:
<div ng-repeat="item in items | orderBy : '-'" collapse="item.deleted">
<div class="list card">
<img class="full-image" ng-src="{{item.image}}"/>
</div>
</div>
As you can see I've injected $timeout and leaving the interval blank in hopes it will wait until the DOM is loaded, but it seems no matter how I use it, the directive still explicitly sets the height of the element in css before the image child element is rendered. How can I delay the setting of element height until after ng-src is loaded in each ng-repeat item?
First thing, angular $timeout without an $interval will not wait for the DOM tree to load, basically, what it does is waiting for the current digest cycle to finish before executing the function in the first parameter. By doing so, it will allow the your code to wait till the directive finish compile and render before calculating the height of the div.
However, there is no guarantee that the image will be loaded by that time. Images are loaded by the browser independently from DOM rendering, therefore, to calculate the height of the container having images precisely, you should make use of JS Image Object and the load event. Once the images are fully loaded, then you can update the height.
Also, for your directive, I don't think you need to calculate the height every times the collapse variable changed (inside the watch), you can simply wait till the image being loaded, calculate the height once, store it inside the scope object, and reuse it whenever the collapse variable change.
Okay, so thanks to Thai's input, I have a working solution:
.directive('collapse', [function () {
return {
restrict: 'A',
link: function ($scope, ngElement, attributes) {
var element = ngElement[0];
var img = element.querySelector('.full-image');
angular.element(img).bind('load', function() {
var autoHeight = getElementAutoHeight();
element.style.height = autoHeight + "px";
});
$scope.$watch(attributes.collapse, function (collapse) {
var newHeight = collapse ? 0 : getElementAutoHeight();
element.style.height = newHeight +"px";
ngElement.toggleClass('collapsed', collapse);
});
function getElementAutoHeight() {
var currentHeight = getElementCurrentHeight();
element.style.height = 'auto';
var autoHeight = getElementCurrentHeight();
element.style.height = currentHeight +"px";
getElementCurrentHeight(); // Force the browser to recalc height after moving it back to normal
return autoHeight;
}
function getElementCurrentHeight() {
return element.offsetHeight
}
}
};
}])

What element is jQuery UI draggable being dragged over in an iframe

Here is my code, where I'm trying to detect the element, which a jQuery UI draggable is hovering over. I need to get the element's object and attributes, such as class names (in this case .sortable-grid,.sortable-table,.sortable-row,.sortable-cell).
The answers found here only show how to get the draggable item itself (ui.helper or event.target), but not the element it is hovering above.
The best way to answer would be using the prepared JSFiddle, since my code uses an iframe, which would not work if the full code is posted here:
JSFiddle
HTML:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0-beta.1/jquery-ui.js"></script>
<div style="background-color:grey;display:inline;cursor:move" id="draggable">DRAG ME</div>
<iframe src="https://fiddle.jshell.net/piglin/UAcC7/1869/show/" id="frame" style="width:100%;overflow:visible" seamless="seamless" scrolling="no"></iframe>
JS:
$("#draggable").draggable({
drag: function(event, ui) {
//Some code here
}
}
It was possible by modifying the function from another answer to fit this purpose. After adapting it to use the contentWindow of the iframe and adding offset calculation it works now.
Solution
function allElementsFromPointIframe(x, y, offsetX, offsetY) {
var element, elements = [];
var old_visibility = [];
while (true) {
element = document.getElementById('frame').contentWindow.document.elementFromPoint(x - offsetX, y - offsetY);
if (!element || element === document.getElementById('frame').contentWindow.document.documentElement) {
break;
}
elements.push(element);
old_visibility.push(element.style.visibility);
element.style.visibility = 'hidden'; // Temporarily hide the element (without changing the layout)
}
for (var k = 0; k < elements.length; k++) {
elements[k].style.visibility = old_visibility[k];
}
elements.reverse();
return elements;
}
var selected = $('');
var tmpColor = 'transparent';
$("#draggable").draggable({
drag: function(event, ui) {
var el = $(allElementsFromPointIframe(event.pageX, event.pageY, $(frame).offset().left, $(frame).offset().top));
var div = $(el).filter('ul, li').not($(this));
selected.css({'backgroundColor': tmpColor});
selected = div.last()
tmpColor = selected.css('backgroundColor');
selected.css({'backgroundColor': 'red'});
console.dir(div);
},
iframeFix: true,
iframeOffset: $('#iframe').offset()
});

copy a div to a window popup with css

I'm trying to take a div and basically put it in a child window. The domains are going to be the same so I figure I could do the flowing ( where parm win is the div ).
popOut = function ( win )
{
var newWindow = window.open( "about:blank", "", "toolbar=yes, scrollbars=yes, resizable=yes, top=500, left=500, width=400, height=400", false );
newWindow.onload = function ()
{
this.focus
this.document.body.appendChild( win );
};
}
This seems to do the job but the css is clobbered. In thinking about this, the new window certainly knows nothing of the includes and css files. Can I copy or inject this info over somehow?
Would I be correct in assuming the new window knows nothing of its parent, thus no function can be ran from child to parent?
New window is definitely aware of its opener as well as opener aware of the popup. If the problem is new window not seeing opener styles, the following code should copy styles from opener to popup.
Place this after var newWindow = window.open(...)
var linkrels = document.getElementsByTagName('link');
for (var i = 0, max = linkrels.length; i < max; i++) {
if (linkrels[i].rel && linkrels[i].rel == 'stylesheet') {
var thestyle = document.createElement('link');
var attrib = linkrels[i].attributes;
for (var j = 0, attribmax = attrib.length; j < attribmax; j++) {
thestyle.setAttribute(attrib[j].nodeName, attrib[j].nodeValue);
}
newWindow.document.documentElement.appendChild(thestyle);
}
}
I haven't got a chance to test it, but something like this should work.

HTML5 Canvas with Predefined Image

Recently I started working on HTML5 Canvas, I'm new to it.
I've a problem as follows:
I'm loading a Canvas with Body Chart Image (Predefined Image) and on that User will Draw some lines, shapes, etc.
After that I'll generate an image object as follows
var canvas = document.getElementById("MyCanvas");
var dataURL = canvas.toDataURL();
var image = new Image();
image.src = dataURL;
But, Here it generates only those elements which are drawn by users (lines, shapes) as PNG Image. It won't take that Predefined canvas background Image.
I need to generate a PNG image which should include both the Canvas background Image as well as User entered drawing elements.
How to do this?
Try to actually draw you image onto your canvas, utilizing these functions:
var canvas = document.getElementById("MyCanvas");
var img = new Image();
img.src = 'pathToYourImageHere';
canvas.drawImage(img,0,0); /* 0,0 is x and y from the top left */
When you now try to save it, it should also save your background image.
EDIT:
In response to your comment:
You can circument your layering problem by using two different canvases. One for the image, and one for your drawing. Then layer them on top of each other using absolute positioning.
You can read more here: Save many canvas element as image
EDIT2:
But actually you shouldn't have a layering problem, since the following code will first draw the image and then draw the arc, and the layering will be fine:
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var imageObj = new Image();
imageObj.src = "somePathToAnImage";
context.drawImage(imageObj, 50, 50);
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 1.1 * Math.PI;
var endAngle = 1.9 * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = "red";
context.stroke();
Even though the layering is fine, you will be better of by using two canvases, in case you would like to only save the drawing, without the background. You can always save both into a new canvas and save that, when you only use one canvas you'll have a hard time separating the drawing from the background.
This is because the image needs time to load, you have to use the onload function.
imgObj.onload = function() { context.drawImage(imageObj, 50, 50); imgLoaded = true;}
if (imgLoaded) { /*you draw shapes here */ }

Resources