Search code examples
canvasfabricjskineticjs

How can I draw text that looks embossed using Kinetic JS and canvas?


Update - Here is my final jsfiddle working version using Kinetic.

I'm trying to show text with two different shadows to create an "embossed" look. I'm using this jsfiddle as a model of the result I'd like to get which uses CSS.

Here is the jsfiddle of what I'm working on currently with Kinetic JS.

var stage = new Kinetic.Stage({
  container: 'stage-container',
  width: 400,
  height: 200
});

var layer = new Kinetic.Layer();
stage.add(layer);

var darkShadow = new Kinetic.Text({
        text: 'Hello',
    x: 140,
    y: 80,
    fill: '#000000',
    fontSize: 60,
    opacity: 0.8,
    filter: Kinetic.Filters.Blur,
    filterRadius: 1
});                         

layer.add(darkShadow);

var lightShadow = new Kinetic.Text({
        text: 'Hello',
    x: 136,
    y: 76,
    fill: '#FFFFFF',
    fontSize: 60,
    opacity: 0.3,
    filter: Kinetic.Filters.Blur,
    filterRadius: 1
});                         

layer.add(lightShadow);

var mainText = new Kinetic.Text({
        text: 'Hello',
    x: 138,
    y: 78,
    fill: '#FFFFFF',
    fontSize: 60,
    opacity: 0.8
});                         

layer.add(mainText);

layer.draw();

I am currently drawing the text 3 times and just offsetting them to get each shadow and then the main text. My issue is that the main text needs to have opacity to bring forth the background color. Here are a few screenshots to see what I'm up against.

Just the shadows...

Just the shadows

With all 3 text objects...

With all 3 text objects

My next thought was to create a clipping mask of the main text to subtract that out of the two shadows, then draw the main text with opacity to bring forth the background color. But I'm not exactly sure how to do that or if there's a better way.


Solution

  • Compositing

    Use "destination-out" to remove pixels.

    If I knew how to use kineticjs I would say just set the layer composite operation for the last text layer to "destination-out" and that will remove the pixels.

    But too much work to sift their documentation so here is the same thing without any frameworks.

    // constants
    const text = "Compositing";
    const blur = 2;
    const highLight = "rgba(100,190,256,0.75)";
    const shadow = "rgba(0,0,0,0.65)";
    const font = "84px arial black";
    const background = "linear-gradient(to right,  #1e5799 0%,#3096e5 100%)";
    const border = "2px solid #6CF"
    
    // create canvas add styles and put on page
    const canvas = document.createElement("canvas");
    const w = (canvas.width = innerWidth - 24) / 2;  // set size and get centers
    const h = (canvas.height = innerHeight - 24) / 2;
    canvas.style.background = background;
    canvas.style.border = border;
    const ctx = canvas.getContext("2d");
    document.body.appendChild(canvas);
    
    // set up font and font rendering alignment
    ctx.font = font;            
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    
    // draw dark shadow
    ctx.shadowBlur = blur; // shadow
    ctx.fillStyle = ctx.shadowColor = shadow;
    ctx.shadowOffsetY = ctx.shadowOffsetX = blur;
    ctx.fillText(text, w, h);
    
    // draw highLight
    ctx.fillStyle = ctx.shadowColor = highLight;
    ctx.shadowOffsetY = ctx.shadowOffsetX = -blur;
    ctx.fillText(text,  w, h);
    
    // draw center text that removes pixels
    ctx.shadowColor = "rgba(0,0,0,0.0)";               // turn off shadow
    ctx.fillStyle = "black";
    ctx.globalCompositeOperation = "destination-out"; // New pixels will remove old pixels making them transparent
    ctx.fillText(text,  w, h);
    ctx.globalCompositeOperation = "source-over";     // restore default composite operation.