canvas的背景无意间来到了前面

Background of canvas unintentionally coming to front

我有 9 个插槽,我希望能够在我的操作对话期间填充元素。 frame() 函数设计了我的交互 canvas 的背景。因此我想一开始就把它叫做once。问题是当我绘制第二个元素时,前一个元素消失了。我已经弄清楚这是为什么了.. frame() 似乎每次都重绘背景。

我尝试在 Default Welcome Intent 的回调中调用 frame() 但没有任何改变。我还使函数 noop()foo() 只调用一次 frame().. 没有结果。 然后,每次将背景置于前面后,我都尝试重新绘制以前的元素,但这也没有用。 我也无法用 div 在 index.html 中重新创建插槽。矩形出现在整个 canvas 框架的上方或下方。

有人知道如何解决这个问题吗?或者您认为最简单的解决方案是创建一个背景的 .jpg 并将其设置为 html 文件中 canvas 的 style="background url('...')"

感谢任何帮助。

'use strict;'
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');

// failed attempt to redraw previous elements after
// unintentionally bringing background to front:
var prevReqs=[]; //see line 26 and 78

var iD=1;
var requirements = [
  new Requirement(50,50),
  new Requirement(500,50),
  new Requirement(950,50),
  new Requirement(50,250),
  new Requirement(500,250),
  new Requirement(950,250),
  new Requirement(50,450),
  new Requirement(500,450),
  new Requirement(950,450),
];

const callbacks = {
  onUpdate(state) {
    if ('iD' in state) {
      iD = state.iD;
      // prevReqs.push(iD);
      drawRec();
    }
  },
};

function drawRec(){
  requestAnimationFrame(drawRec); //enable drawing
  requirements[iD-1].drawRequirement();
}

function Requirement(xCoord,yCoord){
  this.xCoord=xCoord;
  this.yCoord=yCoord;
  this.drawRequirement = function(){ //draw the element
    ctx.beginPath();
    ctx.lineWidth="1";
    ctx.strokeStyle="#000000";
    ctx.fillStyle="#FFFFFF";
    ctx.rect(xCoord,yCoord,200,100);
    ctx.fill();
    ctx.stroke();
    ctx.moveTo(xCoord,yCoord+33);
    ctx.lineTo(xCoord+200,yCoord+33);
    ctx.stroke();
    ctx.font = "12px Arial";
    ctx.fillStyle = "#000000";
    ctx.strokeStyle="#000000";
    ctx.textAlign = "left";
    ctx.fillText("id: "+iD, xCoord+5, yCoord+47);
  }
}

//background... the problematic function
function frame(){
  requestAnimationFrame(frame);
  ctx.fillStyle="#D6EAF8";
  ctx.strokeStyle="#D6EAF8";
  ctx.rect(0,0,1200,600);
  ctx.stroke();
  ctx.fill();
  for(let i=0;i<requirements.length;i++){
    ctx.beginPath();
    ctx.rect(requirements[i].xCoord,requirements[i].yCoord,200,100);
    ctx.font = "64px Arial";
    ctx.fillStyle = "#DCDCDC";
    ctx.strokeStyle="#DCDCDC";
    ctx.textAlign = "center";
    ctx.lineWidth = "6";
    ctx.fillText(i+1, requirements[i].xCoord+100, requirements[i].yCoord+72);
    ctx.stroke();
  }
  // for(let p=0;p<prevReqs.length;p++){
  //    if(prevReqs[p]!=null){
  //     requirements[prevReqs[p]].drawRequirement();
  //    }
  // }
}

// attempt to call frame() only once
function noop() {};
function foo() {
    foo = noop;
    frame();
}
foo();

// frame(); //?

drawRec(); //uncomment to test in html
assistantCanvas.ready(callbacks);
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>test</title>

    <style type="text/css">
        canvas {
            border: 6px solid navy;
            background: #D6EAF8;
        }
    </style>

    <!-- Disable favicon requests -->
    <link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">

    <!-- Load Assistant Canvas CSS and JavaScript -->
    <link rel="stylesheet" href="https://www.gstatic.com/assistant/immersivecanvas/css/styles.css">
    <script src="https://www.gstatic.com/assistant/immersivecanvas/js/immersive_canvas_api.js"></script>

  </head>
  <body>
    <canvas width="1200" height="600" padding-left="56px"></canvas>
    <script src="js/main.js"></script>
    <!--<script src="js/log.js"></script>-->
  </body>
</html>

'use strict';

const {
  dialogflow,
  Suggestions,
  ImmersiveResponse,
} = require('actions-on-google');
// Import the firebase-functions package for deployment.
const functions = require('firebase-functions');
// Instantiate the Dialogflow client.
const app = dialogflow({debug: true});
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);

// Handle the Dialogflow intent named 'Default Welcome Intent'.
app.intent('Default Welcome Intent', (conv) => {
  conv.ask('Hello');
  conv.ask(new ImmersiveResponse({
    url: `https://${firebaseConfig.projectId}.firebaseapp.com`,
  }));
});

// Handle the Dialogflow intent named 'PlaceElement'
app.intent('PlaceElement', (conv, {number}) => {
  if(number>0 && number<10){
    conv.ask('Ok...');
    conv.ask(new ImmersiveResponse({
      state: {
        iD: number,
      },
    }));
  }
  else {
    conv.ask('Wrong input.');
  }
});

exports.yourAction = functions.https.onRequest(app);

我怀疑 requestAnimationFrame() 并没有按照您的想法去做(根据您的使用方式和旁边的评论来判断)。它不是"enable drawing"。

它所做的是请求在下一次渲染引擎将要进行重绘之前调用如此命名的函数。这可用于更新动画(因此得名),但也有 non-animation 用途。当在动画中使用时,您通常有执行绘图调用的函数 requestAnimationFrame() 再次命名相同的函数 - 将其置于无限循环中。

这就是你在这里所做的。你 认为 你只是调用 frame() 一次,但是因为在里面,你有行

requestAnimationFrame(frame);

它将呈现您在函数中拥有的内容,然后在它之前 re-renders 布局(它通常愿意每秒执行 60 次左右),它 re-calls 您的函数.一遍又一遍。

所以 frame() 不断被调用的原因是因为它要求它不断被调用。

根据您的实际需要,您可以采用多种方法:

  1. 只需删除该行即可。除非你正在做动画,否则没有必要。
  2. 如果您正在根据iD的值制作动画,那么您的onUpdate只需设置iD的值,然后frame() 根据 iD 画东西。

顺便说一句,这在您的情况下并非如此,但 post webhook 代码的一个原因是要准确查看您在 ImmersiveResponse 对象中发送的内容。如果您每次都发送 url 参数,那么您 每次都重新加载页面(并因此重新绘制背景)。