Как мога да разбера кога HTML5 Canvas е приключил с рисуването?

Аз съм начинаещ в javascript. Опитвам се да преоразмеря изображения от страна на клиента, преди да ги кача на моя сървър за съхранение. Намерих начин да го направя с помощта на HTML5 Canvas: http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/

Моят проблем: Продължава да качва празно изображение. Мисля, че причината за празното изображение е, че платното не е приключило с рисуването, когато хванах изображението от платно. Когато задам 3 секунди windows.timeout преди да взема изображение от платно, всичко работи добре. Има ли по-надежден метод от таймаут? Или има някакъв друг метод за преоразмеряване на изображение от страна на клиента?

Причината, поради която правя преоразмеряване на изображения от страна на клиента, е, че имам работа с хора, които качват ненужни големи файлове и мисля, че е най-ефективно да направя преоразмеряване на изображения от страна на клиента.

Благодаря ви за всякакви съвети/отговори!

//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;
      }
    };
  }
]);


person Danil    schedule 13.01.2015    source източник
comment
Добро обяснение, но ако можете да публикувате и код, ще бъде страхотно   -  person Breakpoint    schedule 13.01.2015
comment
@Breakpoint Благодаря, вмъкнах моя код. BTW Опитвам се да използвам MeanJS. Така че моят код е angularJS от страна на клиента   -  person Danil    schedule 13.01.2015


Отговори (3)


Вашият проблем е доста прост.

JavaScript ще изпълнява един израз след друг (и вие се нуждаете от това, защото искате всички оператори да се изпълняват, когато вземете изображението), стига да не се прикачвате към никакви събития.

Ако прикачите кода си към събитие (image.onload например), тази част от кода ви ще изчака събитието, а останалата част от кода ще продължи асинхронно.

Така че сте прав - изображението не се изчертава, когато се опитате да вземете това.

Неговият лош стил да го решите чрез изчакване (най-неефективно/несигурно) по-добре решете това, като обвиете кода си за хващане на изображения във функция и стартирайте тази функция, след като преоразмеряването на вашето изображение приключи. Това е като обратно извикване, което използвате, за да синхронизирате асинхронното събитие.

Пример

/* 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>

person Mx.    schedule 13.01.2015
comment
Така че бях в правилната посока да направя рисунката синхронна. Съжалявам, че задавам този въпрос, но как да го направя синхронен? Търсих в Google и прочетох, че мога да направя това, като задействам събитие или използвам отложени обекти. Но не изискват ли проверка кога е готово платното? - person Danil; 13.01.2015
comment
както посочих в отговора си - обвийте целия си код за извличане на изображения във функция, която може да бъде достъпна от последната част на вашия код за преоразмеряване и я извикайте, както аз нарекох SynchCodeAfterTimeout(). - person Mx.; 13.01.2015
comment
Доколкото мога да кажа, без да го тествам, трябва да извикате кода от края на .onload в function render(src) - person Mx.; 13.01.2015
comment
Много благодаря за изчерпателното обяснение и пример! Ти си най-страхотната! Така че canvas всъщност блокира.. Но onload прави целия блок от код асинхронен... Сега има повече смисъл :) - person Danil; 14.01.2015
comment
Човече, ти ми помагаш толкова много! - person Alejandro Maliachi Quintana; 28.09.2017
comment
Този отговор е много специфичен и не работи в момента, в който трябва да направите много лечение след рисуване. По-добро решение би било да можете да хванете каквото и да е събитие, което се задейства, след като чертежът приключи. - person Mozgor; 08.08.2019

Имах проблем, платното към изображението винаги е празно.

След това сложих забавяне, сега работи добре.

setTimeout(function () {
    var da = document.getElementById("canvasFabric").toDataURL("png", "1");
    $('#FInalImage').attr("src", canvasFabric.toDataURL("png", "1"));
    window.open(da);
}, 100);
person Arun Prasad E S    schedule 18.01.2017
comment
Имам същия проблем. Каква е основната причина? На теория всички чертежи на платно трябва да се синхронизират, така че settimout не би трябвало да има значение. - person levi; 27.08.2020
comment
@levi нямам идея, но не мисля, че това е решение. - person Arun Prasad E S; 27.08.2020
comment
В моя случай изглежда, че е причинено от React изобразяване на целия изглед, който включва платното. - person levi; 27.08.2020

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

Можете да поставите тази част от кода във вашия HTML или някъде във вашите JavaScript файлове само тогава без маркерите на скрипта ;)

Надяваме се, че това работи за вас :D

person ErikBrandsma    schedule 13.01.2015
comment
това няма да работи, ако целта е да се начертае изображение и да се добави текст към платното и ако данните, които трябва да бъдат записани, идват от api,. - person munna ss; 04.08.2020