我该如何知道HTML5 Canvas何时完成绘制?

5
我刚刚开始学习JavaScript。我正在尝试在上传到服务器之前,使用HTML5 Canvas对图像进行客户端大小调整并存储。我已经找到了一种方法:使用HTML5 Canvas,详情请见http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/ 然而,我的问题是:上传的是空白图像。我认为这是画布没有完成绘制就从画布中抓取图像的原因。当我设置3秒的窗口超时,然后再从画布中抓取图像,一切正常。有没有比超时更可靠的方法?或者还有其他方法可以在客户端调整图像大小?
我之所以要进行客户端图片调整大小,是因为我遇到了上传不必要大文件的人,我认为在客户端调整图像大小是最有效的方式。
谢谢您提供任何提示/答案!

//resize image
Resizeimage.resize(file);

//***if i wrap this part with timeout it works
var canvas = document.getElementById('canvas');
var data = canvas.toDataURL(file.type);

// convert base64/URLEncoded data component to raw binary data held in a string
var bytestring;
if (data.split(',')[0].indexOf('base64') >= 0)
  bytestring = atob(data.split(',')[1]);
else
  bytestring = escape(data.split(',')[1]);

//get imagetype
var mimeString = data.split(',')[0].split(':')[1].split(';')[0];

// write the bytes of the string to a typed array
var ia = new Uint8Array(bytestring.length);
for (var i = 0; i < bytestring.length; i++) {
  ia[i] = bytestring.charCodeAt(i);
}

// create a blob from array
var blob = new Blob([ia], {
  type: mimeString
});

//upload files
$scope.upload = $upload.upload({
  url: 'articles/imageUpload',
  method: 'POST',
  file: blob
    //update progress bar
}).progress(function(event) {
  $scope.uploadInProgress = true;
  $scope.uploadProgress = Math.floor(event.loaded / event.total * 100);
  //file upload success
}).success(function(data, status, headers, config) {
  $scope.uploadInProgress = false;
  $scope.uploadedImage = JSON.parse(data);
  $scope.isDisabled = false;
  //file upload fail
}).error(function(err) {
  $scope.uploadInProgress = false;
  console.log('Error uploading file: ' + err.message || err);
});
//***

angular.module('articles').factory('Resizeimage', [
  function() {

    var imgSelectorStr = null;

    // Resizeimage service logic
    function render(src) {
      var image = new Image();

      image.onload = function() {
        var canvas = document.getElementById('canvas');
        
        //check the greater out of width and height
        if (image.height > image.width) {
          image.width *= 945 / image.height;
          image.height = 945;
        } else {
          image.height *= 945 / image.width;
          image.width = 945;
        }

        //draw (&resize) image to canvas
        var ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        canvas.width = image.width;
        canvas.height = image.height;
        ctx.drawImage(image, 0, 0, image.width, image.height);

      };
      image.src = src;
    }

    function loadImage(src) {
      // Create fileReader and run the result through the render
      var reader = new FileReader();

      reader.onload = function(e) {
        render(e.target.result);
      };
      reader.readAsDataURL(src);
    }

    // Public API
    return {
      resize: function(src) {
        loadImage(src);
        return true;
      }
    };
  }
]);


很好的解释,但如果您能够发布一些代码,那就太棒了。 - Breakpoint
@Breakpoint 谢谢,我已经插入了我的代码。顺便说一下,我正在尝试使用MeanJS。所以我的代码是客户端的angularJS。 - Danil
2个回答

3
你的问题非常简单。
JavaScript会按顺序运行一条语句(因为你想在获取图像时执行所有语句),只要你不将其连接到任何事件。如果将代码连接到事件上(例如image.onload),则该部分代码将等待事件,而其余代码将继续异步运行。
因此,你是正确的——当你尝试获取图片时,图片还没有被绘制出来。
通过timeout解决这个问题很糟糕(它效率低下/不安全)。更好的方法是将抓取图像的代码包装在一个函数中,在调整图像大小完成后运行此函数。这就像你用于同步异步事件的回调函数一样。 示例

/* setTimeout will behave like an event */
setTimeout(
  function(){
    $('console').append("<div>SetTimeout</div>");
     SynchCodeAfterTimeout();
  }, 1000);

AsynchCodeAfterTimeout();

function AsynchCodeAfterTimeout(){
    $('console').append("<div>AsynchCodeAfterTimeout</div>");
}

function SynchCodeAfterTimeout(){
    $('console').append("<div>SynchCodeAfterTimeout</div>");
}
console{
  font-family: Consolas, monaco, monospace;
}
<script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>

<console></console>

使用您的代码示例

render();

// Resizeimage service logic
function render() {
  var image = new Image();

  image.onload = function() {
    var canvas = document.getElementById('canvas');

    //check the greater out of width and height
    if (image.height > image.width) {
      image.width *= 945 / image.height;
      image.height = 945;
    } else {
      image.height *= 945 / image.width;
      image.width = 945;
    }

    //draw (&resize) image to canvas
    var ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    canvas.width = image.width;
    canvas.height = image.height;
    ctx.drawImage(image, 0, 0, image.width, image.height);
    /*timeout not needed its just there so the browser renders the canvas contents so you can see them when the alert pops up*/
    setTimeout(function() {
      alert('yey all the drawing is done (this is synchronous)');
    });
  };
  image.src = 'https://loremflickr.com/320/240';
}
<canvas id="canvas"></canvas>


所以我在正确的方向上使绘图同步。很抱歉要问这个问题,但是我该如何使它同步?我谷歌了一下并阅读了一些资料,得知可以通过触发事件或使用延迟对象来实现。但是它们不需要检查画布是否准备好吗? - Danil
正如我在我的答案中指出的那样 - 将所有捕获图像的代码封装在一个函数中,这个函数可以从你重置大小代码的最后一部分访问,并像我调用 SynchCodeAfterTimeout() 一样调用它。 - Mx.
就我所知,如果没有测试过,你应该在 function render(src) 中的 .onload 结尾处调用代码。 - Mx.
非常感谢您提供详细的解释和示例!您真是太棒了!所以说,canvas实际上是阻塞的...但是onload使整个代码块异步化...现在更有意义了 :) - Danil
老兄,你帮了我很多! - Alejandro Maliachi Quintana
这个答案非常具体,但在你需要进行大量处理后才能使用。更好的解决方案是能够捕获绘图完成后触发的任何事件。 - Mozgor

-2
<script type="text/javascript">
     window.onload = function(){
         //Whatever you want to do when it has finished loading here
     }
</script>

你可以将这段代码放在HTML中或者JavaScript文件中,不需要使用脚本标签 ;)

希望这对你有用 :D


如果目标是在画布上绘制图像并添加文本,并且要写入的数据来自API,则此方法将不起作用。 - munna ss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接