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

Posted

技术标签:

【中文标题】HTML5 Canvas:图像上的文本绘制在图像更改时停止显示【英文标题】:HTML5 Canvas: Text drawn on image stops showing when image is changed 【发布时间】:2021-06-11 04:00:47 【问题描述】:

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

目前该应用程序运行良好,除了有一个问题。

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

    上传随机图片。 在输入字段中输入内容。 上传并更改图片。

你会看到文字消失了。

这是代码:

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, 0, canvas.width, canvas.height);
        ctx.drawImage(imgObj, 0, 0);

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

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


        // TEXT2 STYLES
        canvasTxt.font          = 50;
        ctx.fillStyle           = "white";
        canvasTxt.drawText(
            ctx, 
            $(text2).val(),
            20, 
            20, 
            200, 
            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], 16),
        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, 0, 0);
    gradient.addColorStop(0, `rgba($r, $g, $b, $alpha)`);

    ctx.save() // <----------- ADD
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, 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, 0, 0, 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"  ></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 和 event 作为参数调用 imgManipulation 函数。

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

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

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

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

我们将非常感谢您的帮助。

提前致谢

【问题讨论】:

【参考方案1】:

你可以看到画图有两种方式: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', function(e) 
      let imgObj          = new Image();
      imgObj.onload       = draw;
      imgObj.onerror      = failed;
      imgObj.src          = URL.createObjectURL(this.files[0]);

      imgManipulation( e, imgObj );
);  

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

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

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


        // TEXT2 STYLES 
        canvasTxt.fontSize      = 50; // canvasTxt.font = 50;
        ctx.fillStyle           = "white";
        canvasTxt.drawText(
            ctx, 
            $(text2).val(),
            20, 
            20, 
            200, 
            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),
        g: parseInt(result[2], 16),
        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, 0, 0);
    gradient.addColorStop(0, `rgba($r, $g, $b, $alpha)`);

    ctx.save()
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, 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, 0, 0, nw, 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"  ></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>

【讨论】:

【参考方案2】:

有一个时间问题。

在这段代码中:

$(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 元素。明智的做法是只使用一个图像元素并为其提供新的来源,否则长时间的用户会话可能会不必要地填满存储。

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

在 text2 的 fontSize 设置中存在另一个问题,它试图将字体设置为 50 - 这会影响后续重绘时使用的字体大小,因此已将其更改为 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, 0, canvas.width, canvas.height);
            ctx.drawImage(imgObj, 0, 0);

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

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


            // TEXT2 STYLES
            canvasTxt.fontSize          = 50;//was just 'font'
            ctx.fillStyle           = "white";
            canvasTxt.drawText(
                ctx, 
                $(text2).val(),
                20, 
                20, 
                200, 
                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], 16),
            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, 0, 0);
        gradient.addColorStop(0, `rgba($r, $g, $b, $alpha)`);

        ctx.save() // <----------- ADD
        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, canvas.width, 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, 0, 0, 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"  ></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>

【讨论】:

以上是关于HTML5 Canvas:图像上的文本绘制在图像更改时停止显示的主要内容,如果未能解决你的问题,请参考以下文章

在 Angular.js 上的 HTML5 Canvas 中的背景图像上绘制一个矩形

在 HTML5 Canvas 中绘制图像,同时保留图像

HTML5 Canvas - 如何在画布中的图像上绘制矩形

HTML5 Canvas ~ 使用绘制元素平滑快速缩放,而不是图像 ~ 如何?

在画布上绘制图像 - 比真实更大

HTML5 Canvas - 如何在图像背景上画一条线?