skip to Main Content

html2canvas works great for taking screenshot but currently doesn’t capture longer text in textAreas or more than one variable in a multiple-select. See https://github.com/niklasvh/html2canvas/issues/2008 for discussion.

@chawes13 provided excellent suggestions to work around these issues that resulted in the function below. I set height: document.body.scrollHeight to vertically crop the screenshot of the app I’m working on. However, the height can be changed during the onclone process. I have tried lots of different options but the only things that seem to work are either (1) don’t use any vertical cropping or (2) use cropping based on the initial body height which, unfortunately, cuts off the lower part of the image if changes have been made during onclone. The app has lots of tabs and dynamically changes size as different data is loaded or variables are selected, so setting to one fixed height isn’t an option.

Question: Is it possible to determine the (scroll) height of the clonedDocument and use that to adjust the screenshot height when needed?

function generate_screenshot() {
  html2canvas($("body")[0], {
    y: 55,
    height: document.body.scrollHeight,
    onclone: (clonedDocument) => {
      Array.from(clonedDocument.querySelectorAll("textarea")).forEach((textArea) => {
        if (textArea && textArea.value.length > 30) {
          const labelFor = textArea.getAttribute("id")
          const label = clonedDocument.querySelector(`label[for="${labelFor}"]`)
          const div = clonedDocument.createElement("div")
          div.innerText = textArea.value
          div.style.border = "1px solid #d3d3d3"
          div.style.padding = "10px 10px 10px 10px"
          div.style.width = "100%"
          div.style.borderRadius = "5px"
          div.style.boxSizing = "border-box";
          div.style.margin = "0";
          div.style.backgroundColor = "white"
          textArea.style.display = "none"
          textArea.parentElement.append(label, div);
        }
      })

      Array.from(clonedDocument.querySelectorAll('select[multiple]')).forEach((msel) => {
        const multiSelect = document.querySelector("#" + msel.getAttribute("id"));
        if (multiSelect && multiSelect.selectedOptions.length > 1) {
          const clonedMultiSelect = clonedDocument.querySelector("#" + msel.getAttribute("id"));
          const list = clonedDocument.createElement('ul')
          Array.from(multiSelect.selectedOptions).forEach((option) => {
            const item = clonedDocument.createElement('li')
            item.innerHTML = option.value
            item.style = "list-style: none; padding-left: 0.5em"
            item.style.width = "100%"
            list.appendChild(item)
          })
          list.style.border = "1px solid #d3d3d3"
          list.style.padding = "5px 5px 5px 5px"
          list.style.width = "100%"
          list.style.backgroundColor = "white"
          list.style.borderRadius = "5px"
          clonedMultiSelect.style.display = "none"
          clonedMultiSelect.parentElement.appendChild(list)
        }
      });
    },
    ignoreElements: function(el) {
      return el.classList.contains("navbar-inverse") || el.classList.contains("dropdown-menu");
    }
  }).then(canvas => {
    var img = document.createElement("img");
    img.src = canvas.toDataURL("png");
    img.width = parseInt(canvas.style.width);
    img.height = parseInt(canvas.style.height);
    $("#screenshot_preview").empty();
    $("#screenshot_preview").append(img);
  });
}

2

Answers


  1. Ok, no tested, but you can try something like this:

    function generate_screenshot() {
      html2canvas($("body")[0], {
        y: 55,
        onclone: (clonedDocument) => {
          Array.from(clonedDocument.querySelectorAll("textarea")).forEach((textArea) => {
            if (textArea && textArea.value.length > 30) {
              const labelFor = textArea.getAttribute("id")
              const label = clonedDocument.querySelector(`label[for="${labelFor}"]`)
              const div = clonedDocument.createElement("div")
              div.innerText = textArea.value
              div.style.border = "1px solid #d3d3d3"
              div.style.padding = "10px 10px 10px 10px"
              div.style.width = "100%"
              div.style.borderRadius = "5px"
              div.style.boxSizing = "border-box";
              div.style.margin = "0";
              div.style.backgroundColor = "white"
              textArea.style.display = "none"
              textArea.parentElement.append(label, div);
            }
          })
    
          Array.from(clonedDocument.querySelectorAll('select[multiple]')).forEach((msel) => {
            const multiSelect = document.querySelector("#" + msel.getAttribute("id"));
            if (multiSelect && multiSelect.selectedOptions.length > 1) {
              const clonedMultiSelect = clonedDocument.querySelector("#" + msel.getAttribute("id"));
              const list = clonedDocument.createElement('ul')
              Array.from(multiSelect.selectedOptions).forEach((option) => {
                const item = clonedDocument.createElement('li')
                item.innerHTML = option.value
                item.style = "list-style: none; padding-left: 0.5em"
                item.style.width = "100%"
                list.appendChild(item)
              })
              list.style.border = "1px solid #d3d3d3"
              list.style.padding = "5px 5px 5px 5px"
              list.style.width = "100%"
              list.style.backgroundColor = "white"
              list.style.borderRadius = "5px"
              clonedMultiSelect.style.display = "none"
              clonedMultiSelect.parentElement.appendChild(list)
            }
          });
    
          // Dynamically adjust height based on cloned document's scroll height
          var clonedBody = clonedDocument.querySelector("body");
          var clonedHeight = clonedBody.scrollHeight;
          clonedBody.style.height = clonedHeight + "px";
        },
        ignoreElements: function(el) {
          return el.classList.contains("navbar-inverse") || el.classList.contains("dropdown-menu");
        }
      }).then(canvas => {
        var img = document.createElement("img");
        img.src = canvas.toDataURL("png");
        img.width = parseInt(canvas.style.width);
        img.height = parseInt(canvas.style.height);
        // Dynamically adjust width and height based on cloned document's size
        var clonedBody = canvas.parentNode.querySelector("body");
        var clonedWidth = clonedBody.offsetWidth;
        var clonedHeight = clonedBody.offsetHeight;
        canvas.style.width = clonedWidth + "px";
        canvas.style.height = clonedHeight + "px";
        $("#screenshot_preview").empty();
        $("#screenshot_preview").append(img);
      });
    }
    

    Essentially i moved the "onclone" to the beginning, before the height and y options. Inside the onclone function, i added the code to adjust the height dynamically based on the cloned document’s scroll height.

    Login or Signup to reply.
  2. I had this problem once when I tried to create a PDF from a report page. Here’s my code. It not only prints everything as it splits the pages.

    splitImages(canvas: HTMLCanvasElement, maxHeight: number) {
        let y = 0;
        let images = [];
        let acanvas = [];
        let ctx = canvas.getContext('2d');
        while (y < canvas.height) {
            let height = y + maxHeight > canvas.height ? canvas.height - y : y + maxHeight;
            let imgData = ctx.getImageData(0, y, canvas.width, y + height);
            let newCanvas = document.createElement('canvas');
            newCanvas.width = canvas.width;
            newCanvas.height = maxHeight;
            let ctxNewCanvas = newCanvas.getContext('2d');
            ctxNewCanvas.putImageData(imgData, 0, 0);
            if (y + maxHeight < canvas.height)
                this.trimCanvas(newCanvas);
            acanvas.push(newCanvas);
            images.push(newCanvas.toDataURL());
            y = y + newCanvas.height;
        }
        return { images, acanvas };
    }
    
    exportPDF(div_id: string, relName: string, mode: string = 'table', trimCanvas: boolean = true) {
        let data: HTMLElement = document.getElementById(div_id);
        html2canvas(data, {
            windowHeight: data.scrollHeight,
            scrollY: -window.scrollY,
            onclone: doc => {
                doc.querySelector('#' + div_id).removeChild(doc.querySelector("#btnExport"));
                (<HTMLElement>doc.querySelector('#relLogo span')).style.display = 'flex';
                if (mode == 'table') {
                    let tr = document.createElement('tr');
                    let numCols = doc.querySelector('tbody tr').children.length;
                    tr.innerHTML = "<td colspan='" + numCols + "'></td>";
                    doc.querySelector('tbody').lastChild.after(tr);
                }
            }
        }).then(canvas => {
            let pdf: jsPDF = new jsPDF({ orientation: 'landscape', unit: 'px', format: 'a4' });
            if (trimCanvas)
                this.trimCanvas(canvas);
            const dataUrl: string = canvas.toDataURL()
    
            const imgProps: ImageProperties = pdf.getImageProperties(dataUrl);
            let pdfWidth: number = pdf.internal.pageSize.getWidth() * 0.95;
            let pdfHeight: number = pdf.internal.pageSize.getHeight() * 0.925;
            let borderTop: number = pdf.internal.pageSize.getWidth() * 0.025;
            let borderLeft: number = pdf.internal.pageSize.getHeight() * 0.025;
            let maxPxPP = (pdfHeight / pdfWidth) * imgProps.width;
            let canvasSection = this.splitImages(canvas, maxPxPP);
            let imagens: string[] = canvasSection.images;
    
            pdf.addImage(imagens[0], 'PNG', borderLeft, borderTop, pdfWidth, pdfHeight);
            if (imagens.length > 1) {
                imagens.shift();
                imagens.forEach(img => {
                    pdf.addPage('a4', 'l');
                    pdf.addImage(img, 'PNG', borderLeft, borderTop, pdfWidth, pdfHeight);
                })
            }
            pdf.save(relName + '.pdf');
        });
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search