根据所选选项更改画布的尺寸

时间:2018-05-09 12:35:28

标签: javascript css html5 canvas

我在画布上工作,遇到了改变立方体尺寸的想法。因此,通过使用HTML5 Canvas,我组成了这个立方体,它有两个由线条连接起来的正方形,使它看起来像一个立方体。

我想要的是当我从选择中选择立方体类型时,立方体应根据所选选项的长度和宽度自动更改。高度保持不变。就像我选择5x5的立方体一样,默认情况下是立方体,但是当我选择5x10的选项时,宽度(正面)不应该改变,但是立方体的长度(边)应该扩展,反之亦然,如果我选择10x5我的最大选项是25x15。正如您所看到的,我在下面创建的画布以像素为单位,首先我需要将这些像素转换为厘米(cm),然后是厘米到立方米。

整个立方体应在指定的固定画布区域中对齐。

这是fiddle

var canvas = document.querySelector('canvas');

canvas.width = 500;
canvas.height = 300;

var contxt = canvas.getContext('2d');

//squares
/*
contxt.fillRect(x, y, widht, height);
*/
contxt.strokeStyle = 'grey';
var fillRect = false;
contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
contxt.rect(80, 80, 100, 100);
contxt.rect(120, 40, 100, 100);
if (fillRect) {
  contxt.fill();
}
contxt.stroke();

/*Lines
contxt.beginPath();
contxt.moveTo(x, y);
contxt.lineTo(300, 100);
*/
contxt.beginPath();

contxt.moveTo(80, 80);
contxt.lineTo(120, 40);

contxt.moveTo(180, 80);
contxt.lineTo(220, 40);

contxt.moveTo(80, 180);
contxt.lineTo(120, 140);

contxt.moveTo(180, 180);
contxt.lineTo(220, 140);

contxt.stroke();
canvas {
  border: 1px solid #000;
}
select {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select>
  <option>5x5</option>
  <option>5x10</option>
  <option>10x5</option>
</select>

<canvas></canvas>

2 个答案:

答案 0 :(得分:16)

绘制多维数据集

要生成动态多维数据集,您必须在<select>元素上侦听onChange事件。每次所选选项发生更改时,您都希望重绘多维数据集。

要重新绘制多维数据集,您需要创建一个renderCube函数,该函数应采用多维数据集的新维度并指定偏移量以进行定位。在此功能中,您必须清除先前绘制的立方体并使用给定的尺寸和偏移重新绘制新的立方体。

添加转换效果

由于无法将css过渡应用于canvas元素,因此您必须自己实现过渡。您必须创建一个动画函数,该函数将在过渡阶段计算立方体的尺寸,并将其重新渲染到每个帧的屏幕上。

具有过渡效果的可调整大小的多维数据集的实现将是
if you prefer here is a fiddle too
if you do not need the transition effect check the fiddle before it has been implemented

&#13;
&#13;
var canvas = document.querySelector('canvas');
canvas.width = 320;
canvas.height = 150;
var contxt = canvas.getContext('2d');

var currentHeight = 0, currentWidth = 0, currentDepth = 0, animationId = 0;

function renderCube(height, width, depth, offsetX, offsetY) {
  currentHeight = height;
  currentWidth = width;
  currentDepth = depth;

  // Clear possible existing cube
  contxt.clearRect(0, 0, canvas.width, canvas.height);
  contxt.beginPath();

  // Calculate depth, width and height based on given input
  depth = (depth * 10 * 0.8) / 2;
  width = width * 10;
  height = height * 10;

  // Draw 2 squares to the canvas
  contxt.strokeStyle = 'grey';
  var fillRect = false;
  contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
  contxt.rect(offsetX, offsetY, width, height);
  contxt.rect(offsetX + depth, offsetY - depth, width, height);
  if (fillRect) {
    contxt.fill();
  }
  contxt.stroke();


  // An array which specifies where to draw the depth lines between the 2 rects
  // The offset will be applied while drawing the lines
  var depthLineCoordinates = [
    // posX, posY, posX2, posY2
    [0, 0, depth, -depth],
    [width, 0, width + depth, -depth],
    [0, height, depth, height - depth],
    [width, height, width + depth, height - depth]
  ];

  // Draw the depth lines to the canvas
  depthLineCoordinates.forEach(function(element) {
    contxt.moveTo(offsetX + element[0], offsetY + element[1]);
    contxt.lineTo(offsetX + element[2], offsetY + element[3]);
  });
  contxt.stroke();
}

// As requested by OP an example of a transition to the cube
// The transitionDuration may be a double which specifies the transition duration in seconds
function renderCubeWithTransistion(height, width, depth, offsetX, offsetY, transitionDuration) {
  var fps = 60;
  var then = Date.now();
  var startTime = then;
  var finished = false;

  var heightDifference = (height - currentHeight);
  var widthDifference = (width - currentWidth);
  var depthDifference = (depth - currentDepth);

  // Get an "id" for the current animation to prevent multiple animations from running at the same time.
  // Only the last recently started animation will be executed.
  // If a new one should be run, the last one will get aborted.
  var transitionStartMillis = (new Date()).getMilliseconds();
  animationId = transitionStartMillis;

  function animate() {
    // Do not continue rendering the current animation if a new one has been started
    if (transitionStartMillis != animationId) return;
    // request another frame if animation has not been finished
    if (!finished) requestAnimationFrame(animate);

    // Control FPS
    now = Date.now();
    elapsed = now - then;

    if (elapsed > (1000 / fps)) {
      then = now - (elapsed % (1000 / fps));

      // Calculate a linear transition effect
      if (parseInt(currentHeight, 0) != parseInt(height, 0)) currentHeight += heightDifference / (transitionDuration * fps);
      if (parseInt(currentWidth, 0) != parseInt(width, 0)) currentWidth += widthDifference / (transitionDuration * fps);
      if (parseInt(currentDepth, 0) != parseInt(depth, 0)) currentDepth += depthDifference / (transitionDuration * fps);

      // Render the cube
      renderCube(currentHeight, currentWidth, currentDepth, offsetX, offsetY);

      // Check if the current dimensions of the cube are equal to the specified dimensions of the cube
      // If they are the same, finish the transition
      if (parseInt(currentHeight, 0) === parseInt(height, 0) && parseInt(currentWidth, 0) === parseInt(width, 0) && parseInt(currentDepth, 0) === parseInt(depth, 0)) {
        finished = true;
      }
    }
  }

  // Start the animation process
  animate();

  return true;
}

// Draw the cube initially with 5x5
renderCube(5, 5, 5, 80, 70);

// Add the onChange event listener to the select element
var cubeSizeSelector = document.getElementById('cubeSizeSelector');
cubeSizeSelector.onchange = function(e) {
  var cubeSize = e.target.value.split('x');
  renderCubeWithTransistion(5, parseInt(cubeSize[0], 0), parseInt(cubeSize[1], 0), 80, 70, 0.3);
}
&#13;
canvas {
  border: 1px solid #000;
}
select {
  display: block;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"> </script>
<select id="cubeSizeSelector">
  <option>5x5</option>
  <option>5x10</option>
  <option>10x5</option>
</select>

<canvas></canvas>
&#13;
&#13;
&#13;

答案 1 :(得分:7)

绘制拉伸轮廓。 Axonometric

理想情况下,您将创建一个通用的轴测渲染器,给定平面图根据需要将对象渲染到画布。

然后,您可以将计划链接到选择框,并在选择更改后更新视图。

最好作为代码示例

下面的示例使用对象renderIsoPlan来渲染形状。

通过计划设置形状。例如,一个方框的平面图[[-1,-1],[1,-1],[1,1],[-1,1]]代表4个底角。

renderIsoPlan具有以下属性

  • 画布呈现形状的画布。在设置之前不会绘制。 renderIsoPlan将创建一个2D上下文,如果您已经有
  • ,它将是相同的
  • 高度投影轮廓的距离。
  • 样式 Canvas上下文样式对象,例如{stokeStyle : "red", lineWidth : 2}用红线绘制2个像素。
  • 计划地板的积分。点会自动移动到中心。例如[[0,-1],[1,1],[-1,1]]绘制一个三角形
  • 比例比例说不再
  • 旋转要旋转的金额。如果不是0那么投影是二分的,否则它是三分的。
  • 画布的单​​位大小
  • centerY 。即0.5是中心
  • centerX 与centerY相同

调用renderIsoPlan.refresh来绘制

注意你不能在问题中旋转投影,因为它在视觉上看似扭曲(改变形状),因此如果旋转不是0则使用不同的投影。

注意对象自动以0,0为中心,使用centerXcenterY以视图为中心

&#13;
&#13;
setTimeout(start,0); // wait till Javascript parsed and executed
requestAnimationFrame(animate); // Animate checked at start so start anim

// named list of shapes
const boxes = {
  box1By1 : {
    plan : [[-1,-1],[1,-1],[1,1],[-1,1]],
    scale : 35,
    centerY : 0.75,
  },
  box1By2 : {
    plan :  [[-1,-2],[1,-2],[1,2],[-1,2]],
    scale : 30,
    centerY : 0.7,
  },
  box2By2 : {
    plan :  [[-2,-2],[2,-2],[2,2],[-2,2]],
    scale : 25,
    centerY : 0.7,
  },
  box2By1 : {
    plan :  [[-2,-1],[2,-1],[2,1],[-2,1]],
    scale : 30,
    centerY : 0.7,
  },
  box1By3 : {
    plan : [[-1,-3],[1,-3],[1,3],[-1,3]],
    scale : 22,
    centerY : 0.67,
  },
  box1By4 :{
    plan :  [[-1,-4],[1,-4],[1,4],[-1,4]],
    scale : 20,
    centerY : 0.63,
  },
  lShape : {
    plan : [[-2,-4],[0,-4],[0,2],[2,2],[2,4],[-2,4]],
    scale : 20,
    centerY : 0.65,
 },
  current : null,
}
// Sets the renderIsoPlan object to the current selection
function setShape(){
  boxes.current = boxes[boxShape.value];
  Object.assign(renderIsoPlan, boxes.current);
  if (!animateCheckBox.checked) { renderIsoPlan.refresh() }
}
// When ready this is called
function start(){
  renderIsoPlan.canvas = canvas;
  renderIsoPlan.height = 2;
  setShape();
  renderIsoPlan.refresh();
}

// Add event listeners for checkbox and box selection 
boxShape.addEventListener("change", setShape );
animateCheckBox.addEventListener("change",()=>{
  if (animateCheckBox.checked) {
    requestAnimationFrame(animate);
  } else {
    renderIsoPlan.rotate = 0;
    setShape();
  }
});


// Renders animated object
function animate(time){     
  if (animateCheckBox.checked) {
    renderIsoPlan.rotate = time / 1000;
    renderIsoPlan.refresh();
    requestAnimationFrame(animate);
  }
}


// Encasulate Axonometric render.
const renderIsoPlan = (() => {
    var ctx,canvas,plan,cx,cy,w,h,scale,height, rotate;
    height = 50;
    scale = 10;
    rotate = 0;
    const style = {
      strokeStyle : "#000",
      lineWidth : 1,
      lineJoin : "round",
      lineCap : "round",
    };
    const depthScale = (2/3);

    // Transforms then projects the point to 2D
    function transProjPoint(p) {
      const project = rotate !== 0 ? 0 : depthScale;
      const xdx = Math.cos(rotate);
      const xdy = Math.sin(rotate);
      const y = p[0] * xdy + p[1] * xdx;
      const x = p[0] * xdx - p[1] * xdy - y * project;
      return [x,y * depthScale];
    }
    
    // draws the plan        
    function draw() {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0,0,w,h);      
      ctx.setTransform(scale, 0, 0, scale, cx, cy);
      var i = plan.length;
      ctx.beginPath();
      while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
      ctx.closePath();
      i = plan.length;
      ctx.translate(0,-height);
      ctx.moveTo(...transProjPoint(plan[--i]))
      while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
      ctx.closePath();
      i = plan.length;
      while(i--){
        const [x,y] = transProjPoint(plan[i]);
        ctx.moveTo(x,y);
        ctx.lineTo(x,y + height);
      }
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.stroke();

    }
    // centers the plan view on coordinate 0,0
    function centerPlan(plan){
      var x = 0, y = 0;
      for(const point of plan){
         x += point[0];
         y += point[1];
      }
      x /= plan.length;
      y /= plan.length;
      for(const point of plan){
         point[0] -= x;
         point[1] -= y;
      }
      return plan;
    }    
    
    
    // Sets the style of the rendering
    function setStyle(){
      for(const key of Object.keys(style)){
        if(ctx[key] !== undefined){
          ctx[key] = style[key];
        }
      }
    }


  // define the interface
  const API = {
    // setters allow the use of Object.apply 
    set canvas(c) {
      canvas = c;
      ctx = canvas.getContext("2d");
      w = canvas.width;  // set width and height
      h = canvas.height;
      cx = w / 2 | 0;    // get center
      cy = h / 2 | 0; // move center down because plan is extruded up
    },
    set height(hh) { height = hh },
    set style(s) { Object.assign(style,s) },
    set plan(points) { plan = centerPlan([...points])  },
    set scale(s) { scale = s },
    set rotate(r) { rotate = r },
    set centerY(c) { cy = c * h },
    set centerX(c) { cx = c * w },
    
    // getters not used in the demo
    get height() { return height },
    get style() { return style },
    get plan() { return plan },
    get scale() { return scale },
    get rotate() { return r },
    get centerY() { return cy / h },
    get centerX() { return cx / w },
    
    // Call this to refresh the view
    refresh(){
      if(ctx && plan){
        ctx.save();
        if(style){ setStyle() }
        draw();
        ctx.restore();
      }
    }
  }
  // return the interface
  return API;
})();
&#13;
canvas { border : 2px solid black; }
&#13;
<select id="boxShape">
 <option value = "box1By1">1 by 1</option>
 <option value = "box1By2">1 by 2</option>
 <option value = "box2By2">2 by 2</option>
 <option value = "box2By1">2 by 1</option>
 <option value = "box1By3">1 by 3</option>
 <option value = "box1By4">1 by 4</option>
 <option value = "lShape">L shape</option>
</select>
<input type="checkBox" id="animateCheckBox" checked=true>Animate</input><br>
<canvas id="canvas"></canvas>
&#13;
&#13;
&#13;