【问题标题】:How to draw multiple images on canvas and save the canvas as png image如何在画布上绘制多个图像并将画布保存为 png 图像
【发布时间】:2019-10-19 07:30:55
【问题描述】:

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

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

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

在我用来在画布上渲染图像的下方:

$(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>

【问题讨论】:

    标签: javascript html image canvas rendering


    【解决方案1】:

    您的代码有几个问题。

    如果图像位于同一域中,或者您要从中检索图像的网络服务器明确允许使用特殊响应标头 (CORS),则仅允许在画布上绘制图像。

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

    在点击处理程序中,您将循环遍历图像元素并立即调用 saveAsPNG 函数。此时图像尚未绘制到画布上!您需要捕获每张图片的 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>

    【讨论】:

    • 嘿,谢谢您的提示,是的,我将使用来自同一域的图像。我尝试了您的示例,它几乎可以正常工作,它渲染了第三张图像代替了第一张和第二张。
    • 哦,对不起-我错过了。在 onload 处理程序内部context.drawImage(img 当然需要替换为context.drawImage(e.target。我已经修复了上面的示例。
    猜你喜欢
    • 1970-01-01
    • 2015-02-12
    • 2023-03-23
    • 1970-01-01
    • 2019-01-30
    • 1970-01-01
    • 2019-10-16
    • 2018-03-05
    • 2011-07-26
    相关资源
    最近更新 更多