简体   繁体   English

HTML画布:圆形形状(行星)的内部阴影

[英]HTML Canvas: inner shadow for circle shapes (planets)

my intention is to draw semi-circular inner shadows inside circular shapes that represent planets moving around a star (this is part of an educational program I'm working on). 我的意图是在圆形内部绘制半圆形的内部阴影,以表示行星围绕恒星运动(这是我正在研究的教育计划的一部分)。

After many approaches, this was the one that almost worked for me: 经过多种方法,这几乎对我有用:

  1. Draw a circular shape (planet) and over it, stroke a larger circle that contains the actual shadow. 画一个圆形(小行星),然后在其上画一个包含实际阴影的大圆圈。

每个行星都是一个圆,将在其上绘制阴影 2. Using the composition option "ctx.globalCompositeOperation='source-atop';" 2.使用组合选项“ ctx.globalCompositeOperation ='source-atop';” to draw the bigger circle it will only paint the portion that overlaps the existing content: 要绘制更大的圆圈,它只会绘制与现有内容重叠的部分:

只有与内容重叠的区域才会被绘制

But the problem is that any planet will overlap any shadow circle, so, as you can see, when a planet overlaps the bigger shadow it turns totally dark. 但是问题在于, 任何行星都将与任何阴影圆重叠,因此,如您所见,当行星与较大的阴影重叠时,它会完全变暗。

Is there any way to make it draw the overlap area of a specific content (shape)? 有什么方法可以使其绘制特定内容(形状)的重叠区域?

Or, do you know a better way to do this? 或者,您知道更好的方法吗? Remember I must paint the shadow in the specific angle from the planet to the light source. 请记住,我必须以从行星到光源的特定角度绘制阴影。

Thanks in advance! 提前致谢!

Try to call clip method (and related codes) before drawing "shadow" onto planet like this. 在像这样在行星上绘制“阴影”之前,尝试调用clip方法(及相关代码)。

 const ctx = canvas.getContext("2d"); //draw planet ctx.beginPath(); ctx.arc(100, 100, 80, 0, Math.PI*2); ctx.fillStyle = "aqua"; ctx.fill(); //save non-clipped state. ctx.save(); //clip range by planet area. ctx.clip(); //draw shadow ctx.beginPath(); ctx.arc(200, 200, 200, 0, Math.PI*2); ctx.lineWidth = 100; ctx.stroke(); //dispose clip range. ctx.restore(); 
 <canvas id="canvas" width="200" height="200"></canvas> 

Pre render the shadows. 预渲染阴影。

Cool solution for you solar system shadow. 凉爽的解决方案,为您的太阳系阴影。

Some devices do not like rendering the shadow, and all the masking ops during rendering will take away from any other FX you may add. 有些设备不喜欢渲染阴影,并且渲染过程中的所有遮罩操作都会与您可能添加的任何其他FX无关。

One way to do the shadows is to render a shadow for each planet at the start. 制作阴影的一种方法是在开始时为每个行星渲染一个阴影。 Mask it so it fits the planet perfectly. 遮盖它,使其完全适合地球。 During animation just draw the planet, then rotate the shadow image to face the sun and call drawImage to get the same effect as you had and many times quicker. 在动画过程中,只需绘制行星,然后旋转阴影图像以面对太阳,然后调用drawImage即可获得与以前相同的效果,并且快很多倍。

Example

The function createShadow creates a custom shadow image for a planet and adds it to the planet object as planet.shadow . 函数createShadow为行星创建自定义阴影图像,并将其作为planet.shadow添加到行星对象。 The function drawPlanet draws the planet first and then draws the shadow over it with normal source-over compositing. 函数drawPlanet绘制行星,然后使用常规的source-over合成合成在其上绘制阴影。

  var canvas = document.createElement("canvas"); canvas.width = canvas.height = 1024; var ctx = canvas.getContext("2d"); document.body.appendChild(canvas); const shadowImageSafeEdge = 2; // pixel safe border around shadow image const shadowBlur = 0.8; // fraction of planet radius var sun = { x : canvas.width /2, y : canvas.height / 2, radius : 80, color : "yellow", } var sunGrad = ctx.createRadialGradient(0, 0, sun.radius/4, 0, 0, sun.radius); sunGrad.addColorStop(0,"#FF7"); sunGrad.addColorStop(0.6,"#FF4"); sunGrad.addColorStop(0.8,"#FF0"); sunGrad.addColorStop(1,"#DC0"); sun.color = sunGrad; function rInt(min,max){ return Math.floor((max-min) * Math.random() + min); } function randCol(hue){ var col = "hsl("; col += Math.floor(hue + rInt(-30,30) + 360) % 360; col += ","; col += Math.floor(80 + rInt(-20,20) + 100) % 100; col += "%,"; col += Math.floor(50 + rInt(-10,10) + 100) % 100; col += "%)"; return col; } // creates a planet at orbit distance from sun function createPlanet(orbit){ var planet = { radius : Math.random() * 20 + 5, orbitDist : orbit, // dist from sun orbitPos : Math.random() * Math.PI * 2, shadow : null, } planet.color = randCol(rInt(280, 360)); planet.shadow = createShadow(planet); return planet; } // creates a shadow image that fits the planet function createShadow(planet){ var r = planet.radius; var s = shadowImageSafeEdge; var planetShadow = document.createElement("canvas"); planetShadow.width = planetShadow.height = r * s + s * 2; // a little room to stop hard edge if zooming var ctx = planetShadow.ctx = planetShadow.getContext("2d"); ctx.shadowBlur = r * shadowBlur ; ctx.shadowOffsetX = ctx.shadowOffsetY = 0; ctx.lineWidth = r * 2 - r * (1 - shadowBlur / 2); ctx.strokeStyle = ctx.shadowColor = "rgba(0,0,0,1)"; ctx.beginPath(); ctx.arc(-planet.orbitDist - r,r + s, planet.orbitDist + r * 2 + r * (shadowBlur /0.85) + s, 0, Math.PI * 2); ctx.stroke(); ctx.stroke(); ctx.stroke(); ctx.shadowColor = "rgba(0,0,0,0)"; ctx.globalCompositeOperation = "destination-in"; ctx.beginPath(); ctx.arc(r + s, r + s, r, 0, Math.PI * 2); // sun will be along x axis ctx.fill(); ctx.globalCompositeOperation = "source-over"; return planetShadow; } // draws the planet and the shadow function drawPlanet(planet){ var xdx = Math.cos(planet.orbitPos); var xdy = Math.sin(planet.orbitPos); var x = xdx * planet.orbitDist + sun.x; var y = xdy * planet.orbitDist + sun.y; ctx.setTransform(1,0,0,1,x,y); ctx.fillStyle = planet.color; ctx.beginPath(); ctx.arc(0,0,planet.radius,0,Math.PI * 2); ctx.fill(); // set transform so that shadow faces away from the sun ctx.globalAlpha = 0.8; ctx.setTransform(xdx,xdy,-xdy,xdx,x,y); ctx.drawImage(planet.shadow,-planet.radius - 2,-planet.radius - 2); ctx.globalAlpha =1; } // let you guess what this function does function drawSun(){ ctx.fillStyle = sun.color; ctx.setTransform(1,0,0,1,sun.x,sun.y); ctx.beginPath(); ctx.arc(0,0,sun.radius,0,Math.PI * 2); ctx.fill(); } // array of planets and create them var planets = []; (function(){ var i = 10; while(i-- >1){ planets.push( createPlanet( rInt( 60 + i * 40,i * 40 + 100) ) ); } }()); // gradient for background var backGrad = ctx.createRadialGradient(512, 512, sun.radius, 512, 512, Math.sqrt(512 * 512 * 2)); backGrad.addColorStop(0,"#B9E"); backGrad.addColorStop(0.025,"#96A"); backGrad.addColorStop(1,"#624"); // main render loop function render(time){ ctx.setTransform(1,0,0,1,0,0); // reset transform ctx.fillStyle = backGrad; ctx.fillRect(0,0,1024,1024); // clear drawSun(); for(var i = 0; i < planets.length; i++){ // draw all planets planets[i].orbitPos += Math.sqrt(10 / Math.pow(planets[i].orbitDist, 2)); drawPlanet(planets[i]); } requestAnimationFrame(render); } requestAnimationFrame(render); 

I would use several canvas. 我会用几个画布。 I would maintain a "main" canvas and draw individual elements to other canvas, and then blend them into the main canvas. 我将维护一个“主”画布,并将单个元素绘制到其他画布,然后将它们混合到主画布中。

This question has information on blending one canvas to another: Combining two or more Canvas elements with some sort of blending 该问题提供有关将一个画布混合到另一画布的信息: 将两个或多个Canvas元素与某种混合方式结合在一起

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM