【问题标题】:HTML5 Canvas rotate gradient around centreHTML5 Canvas 围绕中心旋转渐变
【发布时间】:2018-01-19 08:09:28
【问题描述】:

我正在尝试围绕画布的中心点旋转渐变,但我无法找到实现这一目标所需进行的正确计算。

我想做的一个例子是:http://victorblog.com/html5-canvas-gradient-creator/。旋转滑块正是我想要做的,但我查看了源代码,逻辑似乎可以使用 cos/sin 简化很多(虽然我不是这方面的专家,因此我问这个问题)。

我发现这个 SO 线程 (Calculate rotation of canvas gradient) 有点帮助,但它围绕中心点旋转,而不是像第一个示例中的旋转滑块那样粘在中心点上。

对此的任何帮助将不胜感激。

谢谢

【问题讨论】:

    标签: javascript html canvas


    【解决方案1】:

    旋转渐变。


    简单的旋转。

    如果您不担心渐变适合画布,那么简单的旋转就可以解决问题。

    首先你必须计算出渐变的最大长度,这样当它是对角线时它仍然适合画布。

    const maxLength = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height);
    

    然后你可以如下创建渐变

    var angle = ?; // The angle in radians
                   // A value of 0 if a gradient from right to left
    const gradient = ctx.createLinearGradient(
         // the start of the gradient added to the center
         canvas.width / 2 + Math.cos(angle) * maxLength * 0.5,
         canvas.height / 2 + Math.sin(angle) * maxLength * 0.5,
         // the end of the gradient subtracted from the center
         canvas.width / 2 - Math.cos(angle) * maxLength * 0.5,
         canvas.height / 2 - Math.sin(angle) * maxLength * 0.5
    )
    

    这可行,但当渐变不沿对角线时会剪切渐变。

    拟合对角线示例

    const eachOf=(a,cb)=>{var i=0;const len=a.length;while(i<len)cb(a[i], i++);};
    const ctx = canvas.getContext("2d");
    const w = canvas.width;
    const h = canvas.height;
    const maxWidth = Math.sqrt(w * w + h * h) / 2;
    // empty colour items are skipped when creating the gradient
    const gradientColours = ["white","blue",,,,"green","yellow","green",,,,"cyan","black"];
    function createRotatedGradient(angle, colors){
        const g = ctx.createLinearGradient(
            w / 2 + Math.cos(angle) * maxWidth,  // start pos
            h / 2 + Math.sin(angle) * maxWidth,      
            w / 2 - Math.cos(angle) * maxWidth,  // end pos    
            h / 2 - Math.sin(angle) * maxWidth      
        );
        // add colours
        eachOf(colors,(col,i)=> col && g.addColorStop(i / (colors.length - 1), col) );
        return g;
    }
    
    function update(timer){
        ctx.fillStyle = createRotatedGradient(timer / 1000,gradientColours);
        ctx.fillRect(0,0,w,h);
        requestAnimationFrame(update);
    }
    requestAnimationFrame(update);
    canvas { border : 2px solid black; }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;&lt;

    这可以通过将渐变的maxWidth 设置为canvas.heightcanvas.width 来修改以适应宽度或高度

    适合高度示例

    const eachOf=(a,cb)=>{var i=0;const len=a.length;while(i<len)cb(a[i], i++);};
    const ctx = canvas.getContext("2d");
    const w = canvas.width;
    const h = canvas.height;
    const maxWidth = h / 2;
    // empty colour items are skipped when creating the gradient
    const gradientColours = ["white","green",,,,"blue","cyan","blue",,,,"yellow","black"];
    function createRotatedGradient(angle, colors){
        const g = ctx.createLinearGradient(
            w / 2 + Math.cos(angle) * maxWidth,  // start pos
            h / 2 + Math.sin(angle) * maxWidth,      
            w / 2 - Math.cos(angle) * maxWidth,  // end pos    
            h / 2 - Math.sin(angle) * maxWidth      
        );
        // add colours
        eachOf(colors,(col,i)=> col && g.addColorStop(i / (colors.length - 1), col) );
        return g;
    }
    
    function update(timer){
        ctx.fillStyle = createRotatedGradient(timer / 1000,gradientColours);
        ctx.fillRect(0,0,w,h);
        requestAnimationFrame(update);
    }
    requestAnimationFrame(update);
    canvas { border : 2px solid black; }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;&lt;

    适合宽度示例

    const eachOf=(a,cb)=>{var i=0;const len=a.length;while(i<len)cb(a[i], i++);};
    const ctx = canvas.getContext("2d");
    const w = canvas.width;
    const h = canvas.height;
    const maxWidth = w / 2;
    // empty colour items are skipped when creating the gradient
    const gradientColours = ["white","blue",,,,"yellow","green","yellow",,,,"cyan","black"];
    function createRotatedGradient(angle, colors){
        const g = ctx.createLinearGradient(
            w / 2 + Math.cos(angle) * maxWidth,  // start pos
            h / 2 + Math.sin(angle) * maxWidth,      
            w / 2 - Math.cos(angle) * maxWidth,  // end pos    
            h / 2 - Math.sin(angle) * maxWidth      
        );
        // add colours
        eachOf(colors,(col,i)=> col && g.addColorStop(i / (colors.length - 1), col) );
        return g;
    }
    
    function update(timer){
        ctx.fillStyle = createRotatedGradient(timer / 1000,gradientColours);
        ctx.fillRect(0,0,w,h);
        requestAnimationFrame(update);
    }
    requestAnimationFrame(update);
    canvas { border : 2px solid black; }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;&lt;

    简单的动态贴合

    要同时适应宽度和高度,您可以使用非常简单的 y 轴缩放。这个比例就是宽高的比例。

    const maxLen = canvas.width;
    const aspect = canvas.height / canvas.width;
    const angle = ?
    const gradient = ctx.createLinearGradient(
         // the start of the gradient added to the center
         canvas.width / 2 + Math.cos(angle) * maxLen * 0.5,
         canvas.height / 2 + Math.sin(angle) * maxLen * 0.5 * aspect,
         // the end of the gradient subtracted from the center
         canvas.width / 2 - Math.cos(angle) * maxLen * 0.5,
         canvas.height / 2 - Math.sin(angle) * maxLen * 0.5 * aspect
    )
    

    纵横比缩放示例

    const eachOf=(a,cb)=>{var i=0;const len=a.length;while(i<len)cb(a[i], i++);};
    const ctx = canvas.getContext("2d");
    const w = canvas.width;
    const h = canvas.height;
    const maxWidth = w / 2;
    const aspect = h / w;
    // empty colour items are skipped when creating the gradient
    const gradientColours = ["white","red",,,,"yellow","green","yellow",,,,"red","black"];
    function createRotatedGradient(angle, colors){
        const g = ctx.createLinearGradient(
            w / 2 + Math.cos(angle) * maxWidth,  // start pos
            h / 2 + Math.sin(angle) * maxWidth * aspect,      
            w / 2 - Math.cos(angle) * maxWidth,  // end pos    
            h / 2 - Math.sin(angle) * maxWidth * aspect      
        );
        // add colours
        eachOf(colors,(col,i)=> col && g.addColorStop(i / (colors.length - 1), col) );
        return g;
    }
    
    function update(timer){
        ctx.fillStyle = createRotatedGradient(timer / 1000,gradientColours);
        ctx.fillRect(0,0,w,h);
        requestAnimationFrame(update);
    }
    requestAnimationFrame(update);
    canvas { border : 2px solid black; }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;&lt;

    最合适。

    您提供的example site 拟合了渐变,使其来自最近的边缘,这是一个更好的拟合,但仍然不是完美的拟合,因为渐变有时会太短。由于您有该站点的方法,因此我不会在此处包含它。

    最佳拟合稍微复杂一些,但始终适合画布,这样就不会出现画布溢出或不足的情况(不会从渐变外部设置像素,并且所有渐变都可以在任何角度看到)

    有关信息,请参阅示例。数学取自适合旋转图像的this answer

    最佳拟合示例。

    const eachOf=(a,cb)=>{var i=0;const len=a.length;while(i<len)cb(a[i], i++);};
    const ctx = canvas.getContext("2d");
    const w = canvas.width;
    const h = canvas.height;
    const maxWidth = w / 2;
    const aspect = h / w;
    // empty colour items are skipped when creating the gradient
    const gradientColours = ["black","white",,,,"white","red",,,,,,,,,"yellow","green","yellow",,,,,,,,,"red","black",,,,"black","white"];
    
    function bestFitGradient(angle, colors){
        
        var dist = Math.sqrt(w * w + h * h) / 2; // get the diagonal length    
        var diagAngle = Math.asin((h / 2) / dist); // get the diagonal angle
    
        // Do the symmetry on the angle (move to first quad
        var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
        if(a1 > Math.PI){ a1 -= Math.PI }
        if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) }
        // get angles from center to edges for along and right of gradient
        var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
        var ang2 = Math.abs(diagAngle - Math.abs(a1));
        
        // get distance from center to horizontal and vertical edges
        var dist1 = Math.cos(ang1) * h;
        var dist2 = Math.cos(ang2) * w;
        
        // get the max distance
        var scale = Math.max(dist2, dist1) / 2;
        
        // get the vector to the start and end of gradient
        var dx = Math.cos(angle) * scale;
        var dy = Math.sin(angle) * scale;
        
        // create the gradient
        const g = ctx.createLinearGradient(
            w / 2 + dx,  // start pos
            h / 2 + dy,      
            w / 2 - dx,  // end pos    
            h / 2 - dy      
        );
        // add colours
        eachOf(colors,(col,i)=> col && g.addColorStop(i / (colors.length - 1), col) );
        return g;
    }
    
    
    
    function update(timer){
        ctx.fillStyle = bestFitGradient(timer / 1000,gradientColours);
        ctx.fillRect(0,0,w,h);
        requestAnimationFrame(update);
    }
    requestAnimationFrame(update);
    canvas { border : 2px solid black; }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;&lt;

    【讨论】:

    • 完美答案,非常感谢。你救了我很多痛苦
    猜你喜欢
    • 2012-02-14
    • 2014-09-30
    • 1970-01-01
    • 2011-06-06
    • 2020-08-31
    • 1970-01-01
    • 1970-01-01
    • 2013-09-22
    相关资源
    最近更新 更多