0

I adopted a Javascript 1k demo of swinging grass (http://labs.hyperandroid.com/js1k) for my website. Unfortunately, the grass flickers very strong just in Chrome. Not in Firefox nor in MS Edge.

I already changed the code to use requestAnimationFrame, which did not help.

I have no idea what to look for any further. Any help to avoid the problem in Google Chrome is much appreciated.

Comment right after initial post: Here on stackoverflow is much less flicker using Chrome as on JSFiddle (https://jsfiddle.net/4sqpL1b9/) or my website. I do not unterstand this...

Finally the solution for my situation: 1) add 2 pixel to the end of the blade of grass (see variable 'tuneWidth') and 2) up-scale canvas by 2 and down-scale again with CSS to the original underlying object size. This reduces the flicker in Google Chrome almost complete. At least good enough for me :-). I updated the code snippet below.

// Original version http://labs.hyperandroid.com/js1k
// 2019 MD: Modified for being an transparent overlay on top of HTML id "thisOverlay"
// w, d, thisOverlay, thisDay, thisMonth, thisDoy are set from the initiating HTML/PHP file

var grassId = "grass";
var callbackId = null;
var canvas = null;
var ctx = null;
var garden = null;
var gradient;

var grassBaseColor;
var grassSizeFactor;

var allColors = {
  60:"00FF00", 64:"07FD01", 69:"0EFB02", 73:"15F903", 77:"1CF704", 82:"22F506", 86:"29F307", 91:"2FF208",
  95:"35F009", 99:"3BEE0A",104:"41EC0B",108:"47EA0C",112:"4CE80D",117:"52E70E",121:"57E50F",125:"5CE310",
 130:"61E111",134:"66DF12",138:"6ADD13",143:"6FDC14",147:"73DA15",152:"77D816",156:"7CD617",160:"80D418",
 165:"83D218",169:"87D019",173:"8BCF1A",178:"8ECD1B",182:"91CB1B",186:"94C91C",191:"97C71D",195:"9AC51E",
 200:"9DC41E",204:"A0C21F",208:"A2C020",213:"A5BE20",217:"A7BC21",221:"A9BA21",226:"ABB922",230:"ADB723",
 234:"AFB523",239:"B1B324",243:"B1B024",247:"AFAB25",252:"ADA625",256:"ACA126",261:"AA9C26",265:"A89826",
 269:"A69327",274:"A48E27",278:"A28A28",282:"A18628",287:"9F8128",291:"9D7D28",295:"9B7929",300:"997529",
 304:"977229",309:"966E29",313:"946A2A",317:"92672A",322:"90632A",326:"8E602A",330:"8C5D2A",335:"8B5A2B"
};

(function() {
 Grass = function() {
  return this;
 };

 Grass.prototype = {
   alto_hierba:          0,    // grass height
   maxAngle:             0,    // maximum grass rotation angle (wind movement)
   angle:                0,    // construction angle. thus, every grass is different to others
   coords:               null, // quadric bezier curves coordinates
   color:                null, // grass color. modified by ambient component
   offset_control_point: 3,    // grass base width. greater values, wider at the basement

  initialize : function(canvasWidth,canvasHeight,minHeight,maxHeight,angleMax,initialMaxAngle) {
   // grass start position
   var sx = Math.floor(Math.random()*canvasWidth);
   var sy = canvasHeight;
   // quadric curve middle control point. higher values means wider grass from base to peak
   // try offset_control_x = 10 for thicker grass. default = 1.5
   var offset_control_x = 1.5;
   this.alto_hierba = minHeight + Math.random() * maxHeight;
   this.maxAngle = 10 + Math.random() * angleMax;
   this.angle = Math.random() * initialMaxAngle * (Math.random() < 0.5 ? 1 : -1) * Math.PI / 180;
   // hand crafted value. modify offset_control_x to play with grass curvature slope
   var csx = sx-offset_control_x ;
   // grass curvature. greater values make grass bender. try with:
   //   var csy = sy-this.alto_hierba;   -> much more bended grass
   //   var csy = sy-1;                  -> totally unbended grass
   //   var csy = sy-this.alto_hierba/2; -> original, good looking grass
   var csy = Math.random() < 0.1 ? sy - this.alto_hierba : sy - this.alto_hierba / 2;
   // both bezier curves that conform each grass should have the same middle control point to be parallel
   // play with psx/psy by adding or removing values to slightly modify grass geometry
   var psx = csx;
   // changed var psy = csy; to
   var psy = csy - offset_control_x;
   // the bigger offset_control_point, the wider on its basement. default is 1.5
   this.offset_control_point = 1.5;
   var dx = sx + this.offset_control_point;
   var dy = sy;
   this.coords = [sx,sy,csx,csy,psx,psy,dx,dy];
   // make random grass color 
   this.color = [
    parseInt(grassBaseColor.slice(0,2),16) + Math.random()*20,
    parseInt(grassBaseColor.slice(2,4),16) + Math.random()*50,
    parseInt(grassBaseColor.slice(4,6),16) + Math.random()*20
   ];
  },

  // paint every grass
  // ctx is the canvas2drendering context
  // time for grass animation
  // ambient to dim or brighten every grass
  // returns nothing
  paint : function(ctx,time,ambient) {
   // grass peak position. how much to rotate the peak
   // less values, will make as if there were a softer wind. default is 0.0005
   var inc_punta_hierba = Math.sin(time*0.0005);
   // rotate the point, so grass curves are modified accordingly. if just moved
   // horizontally, the curbe would end by being unstable with undesired visuals
   var ang = this.angle + Math.PI/2 + inc_punta_hierba * Math.PI/180 * (this.maxAngle * Math.cos(time*0.0002));
   var px = this.coords[0] + this.offset_control_point + this.alto_hierba * Math.cos(ang);
   var py = this.coords[1] - this.alto_hierba * Math.sin(ang);
   var c = this.coords;
   ctx.beginPath();
   ctx.moveTo(c[0],c[1]);
   // add some pixel to the end of the blade of grass to make
   // it thicker and therefore less flicker. default is 1
   var tuneWidth = 1;
   // draw it
   ctx.bezierCurveTo(c[0],c[1],c[2],c[3],px-tuneWidth,py);
   ctx.bezierCurveTo(px+tuneWidth,py,c[4],c[5],c[6],c[7]);
   ctx.fillStyle ='rgb(' +
    Math.floor(this.color[0]*ambient) + ',' +
    Math.floor(this.color[1]*ambient) + ',' +
    Math.floor(this.color[2]*ambient) + ')';
   ctx.fill();
  }
 };
})();

function getGrassBaseColor() {
 var doy = thisDoy; // 1..366 (from PHP)
 // get max doy (=max key in color array)
 var maxDoy = Math.max.apply(null,Object.keys(allColors));
 // loop until valid or max color index (=day of year)
 while (!allColors[doy] && doy < maxDoy) { doy++; }
 // just in case...
 doy = Math.min(doy,maxDoy);
 return allColors[doy];
}

function getGrassSizeFactor() {
 return 1;
 var day = thisDay; // 1..30 (from PHP)
 var month = thisMonth; // 1..12 (from PHP)
 if (month == 3) { return day/60; }                  // March: 0.0 - 0,5
 else if (month == 4) { return 0.5 + day/60; }       // April: 0.5 - 1.0
 else if (month >= 5 && month <= 9) { return 1; }    // May-September: 1.0
 else if (month == 10) { return 1 - day/60; }        // October: 1.0 - 0.5
 else if (month == 11) { return 0.5 - day/60; }      // November: 0.5 - 0.0
 return 1;                                           // default: 1.0
}

function clearCanvas() {
 ctx.clearRect(0,0,canvas.width,canvas.height);
}

(function() {
 Garden = function() {
  return this;
 };

 Garden.prototype = {
  grass:   null,
  ambient: 1,
  width:   0,
  height:  0,

  initialize : function(width,height,size) {
   this.width = width;
   this.height = height;
   this.grass = [];
   for(var i = 0; i < size; i++) {
    var thisGrass = new Grass();
    thisGrass.initialize(
     width,
     height,
     5,                      // min grass height. default 5
     height*grassSizeFactor, // max grass height
     20,                     // grass max initial random angle. default 20
     45                      // max random angle for animation. default 45
    );
    this.grass.push(thisGrass);
   }
  },

  paint : function(ctx,time) {
   clearCanvas();
   for(var i = 0; i < this.grass.length; i++) {
    this.grass[i].paint(ctx,time,this.ambient);
   }
  }
 };
})();

function paintGarden(timeStamp) {
 garden.paint(ctx,timeStamp);
 callbackId = requestAnimationFrame(paintGarden);
}

function initGrass() {
 var container = d.getElementById(thisOverlay);
 var thisWidth = container.clientWidth;
 var thisHeight = container.clientHeight;
 // clear current canvas area
 if (ctx) { clearCanvas(); }
 // create canvas only if first run of script, not on resize
 if (!canvas) {
  canvas = d.createElement("canvas");
  canvas.id = grassId;
  canvas.title = "Title";
  container.appendChild(canvas);
 }
 if (canvas) {
  // 2 seems to avoid flickering in Chrome best
  var thisScale = 2;
  ctx = canvas.getContext("2d");
  // up-scale canvas and down-scale again with CSS to underlying object size
  // this - together with the above grass thickness tune - avoids almost complete flicker in Google Chrome
  ctx.canvas.width = thisScale * thisWidth;
  ctx.canvas.height = thisScale * thisHeight;
  ctx.scale(thisScale,thisScale);
  canvas.style.width = thisWidth + "px";
  canvas.style.height = thisHeight + "px";
  garden = new Garden();
  // 3rd parameter is grass density. default is 300
  garden.initialize(thisWidth,thisHeight,300);
  requestAnimationFrame(paintGarden);
 }
}

function resetGrass() {
 cancelAnimationFrame(callbackId);
 initGrass();
}

grassBaseColor = getGrassBaseColor();
grassSizeFactor = getGrassSizeFactor();
w.onresize = resetGrass;
w.onload = initGrass;
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<style>
#header {
 height: 100px;
 margin: 0;
 padding: 0;
 background-color: black;
 position: relative; /* to allow canvas overlay */
}
#grass { /* to allow canvas overlay */
 left: 0;
 position: absolute;
 z-index: 50;
}
</style>
</head>
<body>
<div id='header'></div> 
<script type='text/javascript'>var w = window, d = document, thisOverlay = 'header', thisDay = 7, thisMonth = 3, thisDoy = 66;</script>
</body>
</html>
mdrmdr
  • 3
  • 4
  • For the difference of perfs between here, jsfiddle, and elsewhere, check the sizes of your canvas... – Kaiido Mar 07 '19 at 23:38
  • Good finding. 674 px and above and it flickers. 673 px and below and it is nice. Same effect with a test html file directly in Chrome. No problems at all in Firefox and Edge. Regardless of the width of the canvas. What causes this? – mdrmdr Mar 08 '19 at 07:22
  • Found one possible solution (https://stackoverflow.com/questions/34506036/how-do-i-draw-thin-but-sharper-lines-in-html-canvas) and created an updated example: https://jsfiddle.net/jmLc8tf2/. Solution is to up-scale the canvas dimensions and down-scale it again with CSS. In my eyes not a nice solution but it makes things better... – mdrmdr Mar 08 '19 at 11:55

0 Answers0