HTML5 Canvas:更改图像时在图像上绘制的文本停止显示

问题描述

所以我有一个简单的图像编辑器,我在其中使用画布绘制用户选择的图像和一些文本。也就是说,用户可以上传一张图片,然后如果他们愿意,他们可以添加文本或只是更改图片的渐变。

现在该应用程序运行良好,但有一个问题。

如何找到问题?执行以下操作:

  1. 上传随机图片
  2. 在输入字段中输入内容
  3. 上传并更改图片

您会看到文本消失了。

这是代码

const canvasTxt                 = window.canvasTxt.default;
const canvas                    = document.getElementById('canvas');
const ctx                       = canvas?.getContext('2d');
const btnDownload               = document.querySelector('.btnDownload');
const fileUpload                = document.querySelector('.file-upload');

const text1                     = document.getElementById('text1');
const textForm1                 = document.getElementById('text1-form');
const text2                     = document.getElementById('text2');
const textForm2                 = document.getElementById('text2-form');
const text2ShadowColor          = document.getElementById('text2shadowcolor');
const text2ShadowOffsetY        = document.getElementById('text2shadowoffy');

const imageForm                 = document.getElementById('image-form');
const imageGrad                 = document.getElementById('gradientcolor');
const imageGradOpacity          = document.getElementById('gradientopacity');


$(fileUpload).on('change',function(e) {
      let imgObj          = new Image();
      imgObj.onload       = draw;
      imgObj.onerror      = Failed;
      imgObj.src          = URL.createObjectURL(this.files[0]);

      imgManipulation( e,imgObj );
});    

const imgManipulation = ( e,imgObj ) => {
    $(textForm1).on('change keyup input',updateCanvas);
    $(textForm2).on('change keyup input',updateCanvas);
    $(imageForm).on('change keyup input',updateCanvas);

    function updateCanvas(e) {
        e.preventDefault();
        ctx.clearRect(0,canvas.width,canvas.height);
        ctx.drawImage(imgObj,0);

        createGradient($(imageGrad).val(),$(imageGradOpacity).val());

  
        // TEXT1 STYLES based on user input
        canvasTxt.fontSize      = 70;
        ctx.fillStyle           = "white";
        canvasTxt.drawText(
            ctx,$(text1).val(),200,200
        );


        // TEXT2 STYLES
        canvasTxt.font          = 50;
        ctx.fillStyle           = "white";
        canvasTxt.drawText(
            ctx,$(text2).val(),20,200
        );
    }
};

function hexToRgb(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1],16),g: parseInt(result[2],b: parseInt(result[3],16)
    } : null;
};

function createGradient(hex,alpha) {

    const r = hexToRgb(hex).r.toString();
    const g = hexToRgb(hex).g.toString();
    const b = hexToRgb(hex).b.toString();

    var gradient =  ctx.createLinearGradient(800,0);
    gradient.addColorStop(0,`rgba(${r},${g},${b},${alpha})`);

    ctx.save() // <----------- ADD
    ctx.fillStyle = gradient;
    ctx.fillRect(0,canvas.height);
    ctx.restore() // <----------- ADD
};


function draw() {
    canvas.width        = this.naturalWidth;
    canvas.height       = this.naturalHeight;
    const nw            = this.naturalWidth;
    const nh            = this.naturalHeight;

    ctx.drawImage(this,nw,nh);
};

function Failed() {
    console.error("The provided file Couldn't be loaded as an Image media");
};


$(btnDownload).on('click',function(e) {
    const a = document.createElement('a');
    document.body.appendChild(a);
    a.href = canvas.toDataURL();
    a.download = "canvas-image.png";
    a.click();
    document.body.removeChild(a);
});    
#canvas{
    background-color: transparent; 
    width: 30%; 
    height: auto;
    border: 1px solid #777;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/canvas-txt@3.0.0/build/index.js"></script>

<canvas id="canvas" width="800" height="500"></canvas>

<div>
    <input type="file" class="file-upload" />
    <button class="btnDownload">Download</button>
</div>


<div>
  <form id="text1-form">
    <input type="text" id="text1" placeholder="text 1"/> 
  </form>
</div>

<div>
  <form id="text2-form">
    <input type="text" id="text2" placeholder="text 2"/> 
  </form>
</div>

<div>
  <h2>Image Gradient and Opacity</h2>
  <form id="image-form">
    <input type="color" id="gradientcolor" value="#000000" />
    <input type="range" id="gradientopacity" min="0" max="1" value="0" step="0.05" />
  </form>
</div>


<div>
  <h2>Text2 Shadow Offset X</h2>
  <input type="color" id="text2shadowcolor" value="#000000" />
  <input type="range" id="text2shadowoffy" min="0" max="40" value="0" />
</div>

代码概要:

1:首先我有 fileUpload 事件侦听器。它从用户那里获取图像并创建一个图像对象并将其绘制在画布上。然后以 imgObj 和事件为参数调用 imgManipulation 函数

imgManipulation 函数从文本的输入事件侦听器开始。也就是说,每当输入发生变化时,即用户写了一些东西,updateCanvas 函数就会被调用

updateCanvas 函数实际上是在图像上绘制文本。我正在使用一个名为 canvasTxt 的包,它可以帮助文本成为多行。

更改图像后,如果用户在之前输入的用户文本的输入字段上写一个字母,文本就会神奇地出现。

如何在不删除用户输入的文本的情况下更改图像?

非常感谢您的帮助。

提前致谢

解决方法

存在时间问题。

在这段代码中:

$(fileUpload).on('change',function(e) {
      let imgObj          = new Image();
      imgObj.onload       = draw;
      imgObj.onerror      = failed;
      imgObj.src          = URL.createObjectURL(this.files[0]);

      imgManipulation( e,imgObj );
});

您要求在加载图像时执行绘图功能。然后调用 imgManipulation,它会正确地绘制文本,但会在图像有机会加载之前立即调用。所以它会写入文本(仍然保存在输入元素中),但是当加载图像并调用 draw 时,它会被覆盖。

由于 draw 只是将图像与大多数图像一起写入画布,这将覆盖包含文本的画布顶部。

因此,在加载新图像时,您需要更新整个画布,包括文本。

注意:每次用户选择新图像时都会创建一个新的 img 元素。最好只有一个图像元素并为其提供新的来源,否则长时间的用户会话可能会不必要地填满存储。

此代码段通过在加载新图像时设置超时并在发生这种情况时调用画布更新来说明计时问题。显然,对于生产版本,重构代码以便更新发生在正在加载(或已经加载)的图像上是明智的。

在 text2 的 fontSize 设置中还有一个问题,它试图将 font 设置为 50 - 这影响了后续重绘时使用的 fontsize,因此它已更改为 fontSize。 Text2 也覆盖了 text1 的一部分,但这是一个不同的问题。

        const canvasTxt                 = window.canvasTxt.default;
    const canvas                    = document.getElementById('canvas');
    const ctx                       = canvas?.getContext('2d');
    const btnDownload               = document.querySelector('.btnDownload');
    const fileUpload                = document.querySelector('.file-upload');

    const text1                     = document.getElementById('text1');
    const textForm1                 = document.getElementById('text1-form');
    const text2                     = document.getElementById('text2');
    const textForm2                 = document.getElementById('text2-form');
    const text2ShadowColor          = document.getElementById('text2shadowcolor');
    const text2ShadowOffsetY        = document.getElementById('text2shadowoffy');

    const imageForm                 = document.getElementById('image-form');
    const imageGrad                 = document.getElementById('gradientcolor');
    const imageGradOpacity          = document.getElementById('gradientopacity');

          let imgObj          = new Image();

    $(fileUpload).on('change',function(e) {
          imgObj.onload       = draw;
          imgObj.onerror      = failed;
          imgObj.src          = URL.createObjectURL(this.files[0]);

          setTimeout(function() {imgManipulation(e,imgObj);},1000);// timeout set just to illustrate the timing problem (1sec = long enough for img to load normally)
          //imgManipulation( e,imgObj );
    });    

    const imgManipulation = ( e,imgObj ) => {
        $(textForm1).on('change keyup input',updateCanvas);
        $(textForm2).on('change keyup input',updateCanvas);
        $(imageForm).on('change keyup input',updateCanvas);
        updateCanvas(e);// force an update of canvas each time called,not just on keyups
        function updateCanvas(e) {
            e.preventDefault();
            ctx.clearRect(0,canvas.width,canvas.height);
            ctx.drawImage(imgObj,0);

            createGradient($(imageGrad).val(),$(imageGradOpacity).val());

      
            // TEXT1 STYLES based on user input
            canvasTxt.fontSize      = 70;
            ctx.fillStyle           = "white";
            canvasTxt.drawText(
                ctx,$(text1).val(),200,200
            );


            // TEXT2 STYLES
            canvasTxt.fontSize          = 50;//was just 'font'
            ctx.fillStyle           = "white";
            canvasTxt.drawText(
                ctx,$(text2).val(),20,200
            );
        }
    };

    function hexToRgb(hex) {
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1],16),g: parseInt(result[2],b: parseInt(result[3],16)
        } : null;
    };

    function createGradient(hex,alpha) {

        const r = hexToRgb(hex).r.toString();
        const g = hexToRgb(hex).g.toString();
        const b = hexToRgb(hex).b.toString();

        var gradient =  ctx.createLinearGradient(800,0);
        gradient.addColorStop(0,`rgba(${r},${g},${b},${alpha})`);

        ctx.save() // <----------- ADD
        ctx.fillStyle = gradient;
        ctx.fillRect(0,canvas.height);
        ctx.restore() // <----------- ADD
    };


    function draw() {
        canvas.width        = this.naturalWidth;
        canvas.height       = this.naturalHeight;
        const nw            = this.naturalWidth;
        const nh            = this.naturalHeight;

        //ctx.drawImage(this,nw,nh);
    };

    function failed() {
        console.error("The provided file couldn't be loaded as an Image media");
    };


    $(btnDownload).on('click',function(e) {
        const a = document.createElement('a');
        document.body.appendChild(a);
        a.href = canvas.toDataURL();
        a.download = "canvas-image.png";
        a.click();
        document.body.removeChild(a);
    });
#canvas{
        background-color: transparent; 
        width: 30%; 
        height: auto;
        border: 1px solid #777;
    }
</style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://unpkg.com/canvas-txt@3.0.0/build/index.js"></script>
    
  <canvas id="canvas" width="800" height="500"></canvas>

    <div>
        <input type="file" class="file-upload" />
        <button class="btnDownload">Download</button>
    </div>


    <div>
      <form id="text1-form">
        <input type="text" id="text1" placeholder="text 1"/> 
      </form>
    </div>

    <div>
      <form id="text2-form">
        <input type="text" id="text2" placeholder="text 2"/> 
      </form>
    </div>

    <div>
      <h2>Image Gradient and Opacity</h2>
      <form id="image-form">
        <input type="color" id="gradientcolor" value="#000000" />
        <input type="range" id="gradientopacity" min="0" max="1" value="0" step="0.05" />
      </form>
    </div>


    <div>
      <h2>Text2 Shadow Offset X</h2>
      <input type="color" id="text2shadowcolor" value="#000000" />
      <input type="range" id="text2shadowoffy" min="0" max="40" value="0" />
    </div>

,

你可以看到有两种绘制图像的方法:imgObj.onload = draw;updateCanvas

所以当改变图像时,draw 函数调用而不填充文本。

最好把画布的画东西放在同一个函数里。

像这样。

const canvasTxt                 = window.canvasTxt.default;
const canvas                    = document.getElementById('canvas');
const ctx                       = canvas?.getContext('2d');
const btnDownload               = document.querySelector('.btnDownload');
const fileUpload                = document.querySelector('.file-upload');

const text1                     = document.getElementById('text1');
const textForm1                 = document.getElementById('text1-form');
const text2                     = document.getElementById('text2');
const textForm2                 = document.getElementById('text2-form');
const text2ShadowColor          = document.getElementById('text2shadowcolor');
const text2ShadowOffsetY        = document.getElementById('text2shadowoffy');

const imageForm                 = document.getElementById('image-form');
const imageGrad                 = document.getElementById('gradientcolor');
const imageGradOpacity          = document.getElementById('gradientopacity');


$(fileUpload).on('change',imgObj );
});  

// -----AND `reDraw` function
function reDraw (imgObj) {
        ctx.clearRect(0,canvas.height);
        ctx.drawImage(imgObj,0);

        createGradient($(imageGrad).val(),$(imageGradOpacity).val());

  
        // TEXT1 STYLES based on user input
        canvasTxt.fontSize      = 70;
        ctx.fillStyle           = "white";
        canvasTxt.drawText(
            ctx,200
        );


        // TEXT2 STYLES 
        canvasTxt.fontSize      = 50; // canvasTxt.font = 50;
        ctx.fillStyle           = "white";
        canvasTxt.drawText(
            ctx,200
        );
}

const imgManipulation = ( e,imgObj ) => {
    $(textForm1).on('change keyup input',updateCanvas);
    $(textForm2).on('change keyup input',updateCanvas);
    $(imageForm).on('change keyup input',updateCanvas);

    function updateCanvas(e) {
        e.preventDefault();
        reDraw(imgObj) // <-------- CALL `reDraw`
    }
};

function hexToRgb(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1],16)
    } : null;
};

function createGradient(hex,alpha) {

    const r = hexToRgb(hex).r.toString();
    const g = hexToRgb(hex).g.toString();
    const b = hexToRgb(hex).b.toString();

    var gradient =  ctx.createLinearGradient(800,0);
    gradient.addColorStop(0,${alpha})`);

    ctx.save()
    ctx.fillStyle = gradient;
    ctx.fillRect(0,canvas.height);
    ctx.restore()
};


function draw() {
    canvas.width        = this.naturalWidth;
    canvas.height       = this.naturalHeight;
    // const nw            = this.naturalWidth;
    // const nh            = this.naturalHeight;

    // ctx.drawImage(this,nh);
    reDraw(this) // <-------- CALL `reDraw`
};

function failed() {
    console.error("The provided file couldn't be loaded as an Image media");
};


$(btnDownload).on('click',function(e) {
    const a = document.createElement('a');
    document.body.appendChild(a);
    a.href = canvas.toDataURL();
    a.download = "canvas-image.png";
    a.click();
    document.body.removeChild(a);
}); 
#canvas{
  background-color: transparent; 
  width: 30%; 
  height: auto;
  border: 1px solid #777;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/canvas-txt@3.0.0/build/index.js"></script>

<canvas id="canvas" width="800" height="500"></canvas>

<div>
    <input type="file" class="file-upload" />
    <button class="btnDownload">Download</button>
</div>


<div>
  <form id="text1-form">
    <input type="text" id="text1" placeholder="text 1"/> 
  </form>
</div>

<div>
  <form id="text2-form">
    <input type="text" id="text2" placeholder="text 2"/> 
  </form>
</div>

<div>
  <h2>Image Gradient and Opacity</h2>
  <form id="image-form">
    <input type="color" id="gradientcolor" value="#000000" />
    <input type="range" id="gradientopacity" min="0" max="1" value="0" step="0.05" />
  </form>
</div>


<div>
  <h2>Text2 Shadow Offset X</h2>
  <input type="color" id="text2shadowcolor" value="#000000" />
  <input type="range" id="text2shadowoffy" min="0" max="40" value="0" />
</div>