1

I'm working on a box styling project with Canvas and Javascript, and I can't rotate the text the way I want (written from bottom to the top).

img
(source: hostpic.xyz)

I followed a tutorial (https://newspaint.wordpress.com/2014/05/22/writing-rotated-text-on-a-javascript-canvas/) and tried to adapt it in my code, but I couldn't make it.

img

You can find the code on the JSFiddle link below, there's an input where you can type your text and it should be written as the "brand" word in the JSFiddle example.

https://jsfiddle.net/ParkerIndustries/mgne9x5u/5/

Everything is in the init() function:

function init() {
    var elem = document.getElementById('elem');
    var circle_canvas = document.createElement("canvas");
    var context = circle_canvas.getContext("2d");
    var img = new Image();
    img.src = "https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg";
    context.drawImage(img, 0, 0, 500, 650);
    circle_canvas.width = 500;
    circle_canvas.height = 650;
    context.fillStyle = "#000000";
    //context.textAlign = 'center';
    var UserInput = document.getElementById("UserInput");
    context.save();
    context.translate( circle_canvas.width - 1, 0 );
    UserInput.oninput = function() {
        context.clearRect(0, 0, 500, 650);
        context.drawImage(img, 0, 0, 500, 650);
        var text = UserInput.value;
        console.log(text);
        if (text.length < 12) {
            context.rotate(3*Math.PI/2);
            console.log("-12");
            context.font = "50px Righteous";
            context.fillText(text, -350, -170);
            context.restore();
        } else {
            context.rotate(3*Math.PI/2);
            context.font = "25px Righteous";
            context.fillText(text, -350, -170);
            context.restore();
        }
    }
    elem.appendChild(circle_canvas);
}

init();

I tried a lot a values in the context.rotate() function but in any way my text is upside down.

img
(source: hostpic.xyz)

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
ParkerIndustries
  • 171
  • 2
  • 15

1 Answers1

1

You're pretty close here. I suggest performing the canvas translation to the middle of the screen (width / 2, height / 2) and then rotating by 270 degrees:

context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(270 * Math.PI / 180);

Beyond that, I recommend a round of code cleanup to avoid inconsistent variable names and hardcoded numbers (try to make everything proportional to img.width and img.height, then avoid literal values. This makes it easier to adjust your code dynamically without having to re-type all of the values. You can access img.width and img.height after the img.onload function fires.

Another useful function is context.measureText(text), which makes it simpler to proportionally scale text size.

Full example:

function init() {
  var userInput = document.getElementById("user-input");
  var elem = document.getElementById("elem");
  var canvas = document.createElement("canvas");
  var context = canvas.getContext("2d");
  var img = new Image();
  
  img.onload = function () {
    canvas.width = img.width / 3;
    canvas.height = img.height / 3;
    context.drawImage(img, 0, 0, canvas.width, canvas.height);
    context.fillStyle = "#000000";
    context.textAlign = "center";
    elem.appendChild(canvas);
    
    userInput.oninput = function () {
      var text = userInput.value;
      var textSizePx = 50 - context.measureText(text).width / 10;
      context.font = textSizePx + "px Righteous";
      context.clearRect(0, 0, canvas.width, canvas.height);
      context.drawImage(img, 0, 0, canvas.width, canvas.height);
      context.save();
      context.translate(canvas.width / 2, canvas.height / 2);
      context.rotate(270 * Math.PI / 180);
      context.fillText(text, 0, -canvas.width / 20);
      context.restore();
    }
  };
    
  img.src = "https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg";
}

init();
<div id="elem">
  <input type="text" id="user-input" maxlength="15">
</div>

With that proof-of-concept working, one problem is that scaling the image on canvas causes visual artifacts. This problem can be overcome with JS or by scaling down the source image itself, but at this point, it's best to make the image as an element and circumvent the entire problem.

Similarly, there's no obvious reason to render text on canvas either; we can make an element for this as well. Moving text to HTML/CSS gives more power over how it looks (I didn't do much with style here, so it's an exercise to make it blend more naturally into the image).

Here's a rewrite without canvas that looks much cleaner and should be more maintainable:

function init() {
  var userInput = document.querySelector("#user-input");
  var img = document.querySelector("#product-img");
  var imgRect = img.getBoundingClientRect();  
  var overlayText = document.querySelector("#overlay-text");
  var overlay = document.querySelector("#overlay");
  overlay.style.width = (imgRect.width * 0.86) + "px";
  overlay.style.height = imgRect.height + "px";

  userInput.addEventListener("input", function () {
    overlayText.innerText = userInput.value;
    overlayText.style.fontSize = (50 - userInput.value.length * 2) + "px";
  });
}

init();
#product-img {
  position: absolute;
}

#overlay {
  position: absolute;
  display: flex;
}

#overlay-text {
  font-family: Verdana, sans-serif;
  color: #333;
  transform: rotate(270deg);
  margin: auto;
  cursor: default;
}
<div>
  <input type="text" id="user-input" maxlength="15">
</div>
<div id="product-container">
  <img id="product-img" src="https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg" width=666 height=500 />
  <div id="overlay">
    <div id="overlay-text"></div>
  </div>
</div>
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Thanks @ggorlen for your explanations about your code review. I was wondering if I want to put a second input for the text, how can show it under the first line (I choose the canvas way). Because I tried with creating variables like `var userInput2 = document.getElementById("user-input2"); [...] var text2 = userInput2.value;` But I want a different font and a different size for this one, so variable textSizePx won't work for the second input. Even fillText() won't work for the second input. By the way, sorry for my messy code. I will improve myself. – ParkerIndustries Aug 24 '19 at 08:35
  • 1
    It sounds like you're still using the canvas version, which I don't recommend because it won't look very crisp and is difficult to control and maintain--better to use CSS. But if you are stuck using canvas, it's a matter of doing the same thing as the first text area. and positioning it with `context.fillText(text2, x, y);`. If you use CSS, it's a matter of making another element and positioning it slightly offset from the first, depending on where you need it. Either way, all of the relevant code is here, so it's mostly a copy-and-paste job (or write a function/array to make it extensible). – ggorlen Aug 24 '19 at 16:07
  • 1
    If you're still stuck, you can ask a new question with your updated code and at me and I can take a look. Hope this helps. – ggorlen Aug 24 '19 at 16:09