javascript - 如何在 Canvas 上绘制多个图像并将 Canvas 保存为png图像

标签 javascript html image canvas rendering

你好,我正在尝试在 Canvas 上绘制多个图像,然后使用 html2canvas 和 canvas2image 将其保存为 png。

问题是只有最后一张图片被绘制,当我下载时它没有与图片一起下载。

我正在使用 for 循环遍历图像,然后获取要绘制的 src 值及其偏移量。但我想我在这里遗漏了一些东西,因为只有最后一张图像在渲染,但没有保存在文件中。

下面是我用来在 Canvas 上渲染图像的内容:

$(function() { 
  $("#btnSave").click(function() { 
    html2canvas($("#widget"), {
      onrendered: function(canvas) {
        var context = canvas.getContext("2d"); // returns the 2d context object
        var images = document.getElementsByClassName('simg');
        //Find actual position with parent cotainer

        var offsetsContainer = document.getElementsByClassName('imagesholder')[0].getBoundingClientRect();
        for (var i = 0; i < images.length; i++) {
          var img = new Image() //creates a variable for a new image
          var offsets = images[i].getBoundingClientRect();
          var imgTop = offsets.top;
          var imgLeft = offsets.left;
          imageLeft = parseInt(imgLeft) - parseInt(offsetsContainer.left);
          imageTop = parseInt(imgTop) - parseInt(offsetsContainer.top);
          img.src = images[i].src; // specifies the location of the image
          img.onload = function() {
            context.drawImage(img, imageLeft, imageTop);// draws the image at the specified x and y location
          }
        }
        // Convert and download as image 
        theCanvas = canvas;
        document.body.appendChild(canvas);

        // Convert and download as image 
        Canvas2Image.saveAsPNG(canvas); 
        $("#img-out").append(canvas);
      }
    });
  });
});
body {
    font-family: "Lucida Grande", "Lucida Sans", Arial, sans-serif;
    font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;

}

.dataset {
    float: left;
    vertical-align: top;


}

.widget{
    display: inline-block;
    background-color: white;
    font-size: 14px !important;
    line-height: 20px !important;

    margin: 5px;
    vertical-align: top;
    color: #333;

    border-radius: 5px;
    margin: 10px;
    padding-bottom: 20px;
    border: 1px solid lightgray;


    border-radius:5px;
    -webkit-border-radius: 5px;


    display: inline-block;
    page-break-after: always;

}

.widget .header p {
   padding: 10px;
   border-bottom: 1px solid lightgrey;
   max-width: 360px;
}

.widget .header .title {
    font-weight: bold;
    vertical-align: middle;
    min-height: 36px;
    padding: 5px 10px 5px 10px;

}

.widget .header:hover {
    background-color: #f4f4f4;
}

.widget .header  .title.selected {
    border-color: cornflowerblue;
    background-color: #EEF;

}

.widget .content {
    padding: 5px;
    overflow-y: auto;
    max-height: 400px;
}

.autolayout {
    display: inline-block;
}

.element {
    width: 360px;
}

.compact .content {
    display: table;
    width: 100%;
}

.compact .row {
    display: table-row;
    width: 100%;
}

.compact .cell {
    display: table-cell;
}

.compact .row.selected {
    background-color: #eee;
}



.toolbar {
    display: block;
    vertical-align: top;
    margin: 10px;
}

.toolbar .basis {
    min-width: 100px;
}

.btn {
    /*min-width: 60px;*/
}


.cell.value {
    overflow: hidden;
    text-wrap: none;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-align: right;
    padding-right: 10px;
}

.cell.freq {
    width: 60px;
}

.cell.glyph {
    vertical-align: middle;
    width: 100px;
}


.element {

}
.element table {
    table-layout: fixed;
    width: 100%;
}

.element td {
    padding: 0px;

}

.element .selectable:hover {
    background-color: #f4f4f4;
}


.element .stat {
    text-align: right;
    padding-right: 20px;
    font-weigth: bold;
    color: darkgray;
}



.element .bar {
    height: 18px;
    display: inline-block;
    float: left;
}
.bar-both {
    background-color: #0a67a3 !important;

}

.bar-fg {
    background-color: #3e97d1 !important;
}

.bar-bg {
    background-color: #ddd !important;
}

.selected .bar-fg {
    background-color: #FC0;
}

.selected .bar-both  {
    background-color:#FA0;
}

tr.selected {
    background-color: #eee;
}

.crosstab .selectable:hover {
    background-color: #f4f4f4;
}

.crosstab tr.selected {
    background-color: #eee;
}

.crosstab .header p {
    max-width: 600px;
}


.crosstab td {
  padding: 0 5px 0 5px;
  text-align: right;
}

.crosstab td.value {
  min-width: 60px;
  max-width: 240px;
  text-align: left;
}

.crosstab .cell {
    vertical-align: top;
}



.crosstab th.cell {
    max-width: 120px;
    overflow: hidden;
    white-space: normal;
    text-overflow: ellipsis;
    text-align: right;
    padding-right: 10px;
    vertical-align: bottom;
}

.crosstab .n {
    color: darkgray;
}
.fieldlist {
}

.constraints {
    min-width:300px;
    padding: 10px;

    border-radius:5px;
    -webkit-border-radius: 5px;


}
.constraints table {
    width: 100%;

}

.sidenote {
  max-width:300px;
  padding: 0 10px 0px 10px;
  display: inline-block;
  vertical-align: top;
}

.headnote {
    max-width: 600px;
    padding: 10px;
    margin: 10px;
    display: inline-block;
}

.info-block {
    /*border: 1px solid lightgrey;*/
    background-color: #eee;
    vertical-align: top;
    margin: 10px;
    padding: 10px;
    display: block;

    /*box-shadow: 0 0 0 0px #9bc0cf, 0 0 0 3px #e0ebf0;*/
}

.menu-item {
}

.menu-item-value {
    text-align: right;
    float: right;
}

.gradient-blue {
    background: #b8e1fc;
    background: -moz-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%); /* FF3.6+ */
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b8e1fc), color-stop(10%, #a9d2f3), color-stop(25%, #90bae4), color-stop(37%, #90bcea), color-stop(50%, #90bff0), color-stop(51%, #6ba8e5), color-stop(83%, #a2daf5), color-stop(100%, #bdf3fd)); /* Chrome,Safari4+ */
    background: -webkit-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%); /* Chrome10+,Safari5.1+ */
    background: -o-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%); /* Opera 11.10+ */
    background: -ms-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%); /* IE10+ */
    background: linear-gradient(to bottom, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%); /* W3C */
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b8e1fc', endColorstr='#bdf3fd', GradientType=0); /* IE6-8 */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/canvas2image@1.0.5/canvas2image.min.js"></script>
<script src="https://github.com/niklasvh/html2canvas/releases/download/0.4.1/html2canvas.js"></script>
<div id="widget" class="widget" field="AGE" roundby="20" description="Patient age, in years">
  <div class="header">
    <h3>
      Testing header
    </h3>
  </div>
  <div class="imagesholder">
    <img src="https://lorempixel.com/output/technics-q-c-100-50-2.jpg" class="simg"/>
    <img src="https://lorempixel.com/output/technics-q-c-100-50-8.jpg" class="simg"/>
    <img src="https://lorempixel.com/output/technics-q-c-100-50-3.jpg" class="simg"/>
  </div>
  <div class="element ng-scope">
    <div class="content">
      <table>
        <colgroup>
          <col/>
          <col width="60x"/>
          <col width="100px"/>
        </colgroup>
        <thead>
          <tr>
            <th class="cell value">Value</th>
            <th class="cell freq">Freq</th>
            <th class="cell value"></th>
          </tr>
        </thead>
        <tbody>
          <tr class="selectable">
            <td class="cell value"><span tooltip="0 to 19">0 to 19</span></td>
            <td class="cell freq">17.2%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 17.234468937875754%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr class="selectable">
            <td class="cell value"><span tooltip="20 to 39">20 to 39</span></td>
            <td class="cell freq">18.0%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 18.03607214428858%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr data-key="40" class="selectable">
            <td class="cell value"><span tooltip="40 to 59">40 to 59</span></td>
            <td class="cell freq ng-binding">34.3%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 34.2685370741483%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr class="selectable">
            <td class="cell value"><span tooltip="60 to 79">60 to 79</span></td>
            <td class="cell freq ng-binding">24.0%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 24.04809619238477%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr data-key="80" class="selectable">
            <td class="cell value"><span tooltip="80 to 99">80 to 99</span></td>
            <td class="cell freq">6.4%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 6.4128256513026045%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr>
            <td class="stat">Mean</td>
            <td >46.1</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>
<br/>
<input type="button" id="btnSave" value="Save PNG"/>

<div id="img-out"></div>

最佳答案

您的代码有几个问题。

如果图像位于同一域中,或者如果您从显式检索图像的网络服务器允许使用特殊响应 header (CORS),则允许将图像绘制到 Canvas 上。

为了克服这个问题,我们可以通过代理(例如 https://yacdn.org/serve/)提供图像,并将图像元素的 crossOrigin 属性设置为“匿名”。

在点击处理程序中,您将遍历图像元素并立即调用 saveAsPNG 函数。那时图像还没有绘制到 Canvas 上!您需要捕获每张图片的 onload 事件,并在最后一张图片完成加载和绘制后立即调用 saveAsPNG()。

最后一件事 - 在 onload 处理程序中,您使用变量 imageLeftimageTop 定位每个图像。这意味着您的图像的位置将始终是数组中最后一张图像的值。最好使每个图像实例本身成为这两个单独的变量,例如img.imageLeft=x.

这是一个例子:

$(function() {
  $("#btnSave").click(function() {
    html2canvas($("#widget"), {
      onrendered: function(canvas) {
        var context = canvas.getContext("2d"); // returns the 2d context object
        var images = document.getElementsByClassName('simg');
        //Find actual position with parent cotainer
        var counter = 0;
        var offsetsContainer = document.getElementsByClassName('imagesholder')[0].getBoundingClientRect();
        for (var i = 0; i < images.length; i++) {
          var img = new Image() //creates a variable for a new image
          img.crossOrigin = "Anonymous";
          var offsets = images[i].getBoundingClientRect();
          var imgTop = offsets.top;
          var imgLeft = offsets.left;
          imageLeft = parseInt(imgLeft) - parseInt(offsetsContainer.left);
          imageTop = parseInt(imgTop) - parseInt(offsetsContainer.top);
          img.imageLeft = imageLeft;
          img.imageTop = imageTop;
          // specifies the location of the image

          img.onload = function(e) {
            context.drawImage(e.target, e.target.imageLeft, e.target.imageTop); // draws the image at the specified x and y location
            counter++;
            if (counter == images.length) {
              Canvas2Image.saveAsPNG(canvas);
            }
          }
          img.src = images[i].src;

        }
      }
    });
  });
});
body {
  font-family: "Lucida Grande", "Lucida Sans", Arial, sans-serif;
  font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}

.dataset {
  float: left;
  vertical-align: top;
}

.widget {
  display: inline-block;
  background-color: white;
  font-size: 14px !important;
  line-height: 20px !important;
  margin: 5px;
  vertical-align: top;
  color: #333;
  border-radius: 5px;
  margin: 10px;
  padding-bottom: 20px;
  border: 1px solid lightgray;
  border-radius: 5px;
  -webkit-border-radius: 5px;
  display: inline-block;
  page-break-after: always;
}

.widget .header p {
  padding: 10px;
  border-bottom: 1px solid lightgrey;
  max-width: 360px;
}

.widget .header .title {
  font-weight: bold;
  vertical-align: middle;
  min-height: 36px;
  padding: 5px 10px 5px 10px;
}

.widget .header:hover {
  background-color: #f4f4f4;
}

.widget .header .title.selected {
  border-color: cornflowerblue;
  background-color: #EEF;
}

.widget .content {
  padding: 5px;
  overflow-y: auto;
  max-height: 400px;
}

.autolayout {
  display: inline-block;
}

.element {
  width: 360px;
}

.compact .content {
  display: table;
  width: 100%;
}

.compact .row {
  display: table-row;
  width: 100%;
}

.compact .cell {
  display: table-cell;
}

.compact .row.selected {
  background-color: #eee;
}

.toolbar {
  display: block;
  vertical-align: top;
  margin: 10px;
}

.toolbar .basis {
  min-width: 100px;
}

.btn {
  /*min-width: 60px;*/
}

.cell.value {
  overflow: hidden;
  text-wrap: none;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: right;
  padding-right: 10px;
}

.cell.freq {
  width: 60px;
}

.cell.glyph {
  vertical-align: middle;
  width: 100px;
}

.element {}

.element table {
  table-layout: fixed;
  width: 100%;
}

.element td {
  padding: 0px;
}

.element .selectable:hover {
  background-color: #f4f4f4;
}

.element .stat {
  text-align: right;
  padding-right: 20px;
  font-weigth: bold;
  color: darkgray;
}

.element .bar {
  height: 18px;
  display: inline-block;
  float: left;
}

.bar-both {
  background-color: #0a67a3 !important;
}

.bar-fg {
  background-color: #3e97d1 !important;
}

.bar-bg {
  background-color: #ddd !important;
}

.selected .bar-fg {
  background-color: #FC0;
}

.selected .bar-both {
  background-color: #FA0;
}

tr.selected {
  background-color: #eee;
}

.crosstab .selectable:hover {
  background-color: #f4f4f4;
}

.crosstab tr.selected {
  background-color: #eee;
}

.crosstab .header p {
  max-width: 600px;
}

.crosstab td {
  padding: 0 5px 0 5px;
  text-align: right;
}

.crosstab td.value {
  min-width: 60px;
  max-width: 240px;
  text-align: left;
}

.crosstab .cell {
  vertical-align: top;
}

.crosstab th.cell {
  max-width: 120px;
  overflow: hidden;
  white-space: normal;
  text-overflow: ellipsis;
  text-align: right;
  padding-right: 10px;
  vertical-align: bottom;
}

.crosstab .n {
  color: darkgray;
}

.fieldlist {}

.constraints {
  min-width: 300px;
  padding: 10px;
  border-radius: 5px;
  -webkit-border-radius: 5px;
}

.constraints table {
  width: 100%;
}

.sidenote {
  max-width: 300px;
  padding: 0 10px 0px 10px;
  display: inline-block;
  vertical-align: top;
}

.headnote {
  max-width: 600px;
  padding: 10px;
  margin: 10px;
  display: inline-block;
}

.info-block {
  /*border: 1px solid lightgrey;*/
  background-color: #eee;
  vertical-align: top;
  margin: 10px;
  padding: 10px;
  display: block;
  /*box-shadow: 0 0 0 0px #9bc0cf, 0 0 0 3px #e0ebf0;*/
}

.menu-item {}

.menu-item-value {
  text-align: right;
  float: right;
}

.gradient-blue {
  background: #b8e1fc;
  background: -moz-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);
  /* FF3.6+ */
  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b8e1fc), color-stop(10%, #a9d2f3), color-stop(25%, #90bae4), color-stop(37%, #90bcea), color-stop(50%, #90bff0), color-stop(51%, #6ba8e5), color-stop(83%, #a2daf5), color-stop(100%, #bdf3fd));
  /* Chrome,Safari4+ */
  background: -webkit-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);
  /* Chrome10+,Safari5.1+ */
  background: -o-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);
  /* Opera 11.10+ */
  background: -ms-linear-gradient(top, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);
  /* IE10+ */
  background: linear-gradient(to bottom, #b8e1fc 0%, #a9d2f3 10%, #90bae4 25%, #90bcea 37%, #90bff0 50%, #6ba8e5 51%, #a2daf5 83%, #bdf3fd 100%);
  /* W3C */
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b8e1fc', endColorstr='#bdf3fd', GradientType=0);
  /* IE6-8 */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/canvas2image@1.0.5/canvas2image.min.js"></script>
<script src="https://github.com/niklasvh/html2canvas/releases/download/0.4.1/html2canvas.js"></script>
<div id="widget" class="widget" field="AGE" roundby="20" description="Patient age, in years">
  <div class="header">
    <h3>
      Testing header
    </h3>
  </div>
  <div class="imagesholder">
    <img src="https://yacdn.org/serve/https://lorempixel.com/output/technics-q-c-100-50-2.jpg" class="simg" />
    <img src="https://yacdn.org/serve/https://lorempixel.com/output/technics-q-c-100-50-8.jpg" class="simg" />
    <img src="https://yacdn.org/serve/https://lorempixel.com/output/technics-q-c-100-50-3.jpg" class="simg" />
  </div>
  <div class="element ng-scope">
    <div class="content">
      <table>
        <colgroup>
          <col/>
          <col width="60x" />
          <col width="100px" />
        </colgroup>
        <thead>
          <tr>
            <th class="cell value">Value</th>
            <th class="cell freq">Freq</th>
            <th class="cell value"></th>
          </tr>
        </thead>
        <tbody>
          <tr class="selectable">
            <td class="cell value"><span tooltip="0 to 19">0 to 19</span></td>
            <td class="cell freq">17.2%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 17.234468937875754%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr class="selectable">
            <td class="cell value"><span tooltip="20 to 39">20 to 39</span></td>
            <td class="cell freq">18.0%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 18.03607214428858%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr data-key="40" class="selectable">
            <td class="cell value"><span tooltip="40 to 59">40 to 59</span></td>
            <td class="cell freq ng-binding">34.3%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 34.2685370741483%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr class="selectable">
            <td class="cell value"><span tooltip="60 to 79">60 to 79</span></td>
            <td class="cell freq ng-binding">24.0%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 24.04809619238477%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr data-key="80" class="selectable">
            <td class="cell value"><span tooltip="80 to 99">80 to 99</span></td>
            <td class="cell freq">6.4%</td>
            <td class="cell glyph">
              <span class="bar bar-both" style="width: 6.4128256513026045%;"></span>
              <span class="bar bar-fg" style="width: 0%;"></span>
              <span class="bar bar-bg" style="width: 0%;"></span>
            </td>
          </tr>
          <tr>
            <td class="stat">Mean</td>
            <td>46.1</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>
<br/>
<input type="button" id="btnSave" value="Save PNG" />

<div id="img-out"></div>

关于javascript - 如何在 Canvas 上绘制多个图像并将 Canvas 保存为png图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56432519/

相关文章:

jquery - 如何使用 CSS 为 jQuery 表格中的每一行设置不同的图像

html - 可调整大小图像上的工具提示位置

javascript - 表单验证 - 如何跟踪多个字段以防止提交 - 全局变量?

javascript - 中心背景幻灯片自动调整保持纵横比

基于 css 的页面中的 Javascript 随机图像生成器

javascript - 过渡播放后重置过渡状态

jquery - 在 Twitter Bootstrap 导航栏中将所有元素对齐在一个水平行中

javascript - 计算 2 个日期之间的秒数时获取分钟数

javascript - 在不更改表格布局的情况下使用javascript隐藏部分表格

javascript - 我可以将数组从 Google 电子表格传递到 Google App Script 方法吗?