Angle gradient in canvas

后端 未结 3 1265
隐瞒了意图╮
隐瞒了意图╮ 2020-12-01 16:59

I\'m looking for a code that permits to have this effect on a canvas\' stroke. I\'ve already got an animated circular stroke, I only need to get the ANGLE gradient, not line

相关标签:
3条回答
  • 2020-12-01 17:25

    In my case, I needed the whole circle to be filled rather than just a stroke around the circumference. Using the answer above and setting the line width to twice the radius gave undesirable results, so I wrote my own.

    /**
     * @description Options used when calling CanvasRenderingContext2D.strokeArcGradient() and 
     *              CanvasRenderingContext2D.fillArcGradient().
     * @property {Boolean} useDegrees Whether the specified angles should be interpreted as degrees rather than radians. 
     *                                (default: false)
     * @property {Number} resolutionFactor The number of lines to render per pixel along the arc.  A higher number produces 
     *                                     a cleaner gradient, but has worse performance for large radii.  Must be greater 
     *                                     than 0. (default: 8)
     */
    class ArcGradientOptions {
        constructor(options) {
            function validateParam(test, errorMessage, fatal = false) {
                if (!test) {
                    if (fatal) {
                        throw new Error(errorMessage);
                    } else {
                        console.assert(false, errorMessage);
                    }
                }
            }
    
            options = Object.assign({
                useDegrees: false,
                resolutionFactor: 8,
            }, options);
    
            validateParam(
                (options.resolutionFactor instanceof Number | typeof options.resolutionFactor === 'number') && 
                    options.resolutionFactor > 0, 
                `ArcGradientOptions.resolutionFactor must be a Number greater than 0.  Given: ${options.resolutionFactor}`, 
                true);
    
            Object.assign(this, options);
        }
    };
    
    (function () {
        /**
         * @description Strokes an arc using a linear gradient.
         * @param {number} x The x-component of origin of the arc.
         * @param {number} y The y-component of the origin of the arc.
         * @param {number} radius The radius of the arc.
         * @param {number} startAngle Where in the circle to begin the stroke.
         * @param {number} endAngle Where in the circle to end the stroke.
         * @param {ArcGradientOptions} options Additional options.
         */
        CanvasRenderingContext2D.prototype.strokeArcGradient = function (x, y, radius, startAngle, endAngle, colorStops, 
                options) {
            options = new ArcGradientOptions(options);
            let lineWidth = this.lineWidth;
            this.fillArcGradient(x, y, startAngle, endAngle, colorStops, radius + lineWidth / 2, radius - lineWidth / 2, 
                options);
        }
    
        /**
         * @description Fills a sector or a portion of a ring with a linear gradient.
         * @param {number} x The x-component of origin of the arc
         * @param {number} y The y-component of the origin of the arc
         * @param {number} startAngle Where in the circle to begin the fill.
         * @param {number} endAngle Where in the circle to end the fill.
         * @param {number} outerRadius The radius of the arc.
         * @param {number} innerRadius The radius of the arc that won't be filled.  An innerRadius = 0 will fill the whole 
         *                             arc. (default: 0)
         * @param {ArcGradientOptions} options Additional options.
         */
        CanvasRenderingContext2D.prototype.fillArcGradient = function (x, y, startAngle, endAngle, colorStops, outerRadius, 
                innerRadius = 0, options) {
            options = new ArcGradientOptions(options);
    
            let oldLineWidth = this.lineWidth,
                oldStrokeStyle = this.strokeStyle;
    
            if (options.useDegrees) {
                startAngle = startAngle * Math.PI / 180;
                endAngle = endAngle * Math.PI / 180;
            }
    
            let deltaArcAngle = endAngle - startAngle;
                gradientWidth = Math.floor(outerRadius * Math.abs(deltaArcAngle) * options.resolutionFactor),
                gData = generateGradientImgData(gradientWidth, colorStops).data;
    
            this.lineWidth = Math.min(4 / options.resolutionFactor, 1);
    
            for (let i = 0; i < gradientWidth; i++) {
                let gradi = i * 4,
                    theta = startAngle + deltaArcAngle * i / gradientWidth;
    
                this.strokeStyle = `rgba(${gData[gradi]}, ${gData[gradi + 1]}, ${gData[gradi + 2]}, ${gData[gradi + 3]})`;
    
                this.beginPath();
                this.moveTo(x + Math.cos(theta) * innerRadius, y + Math.sin(theta) * innerRadius);
                this.lineTo(x + Math.cos(theta) * outerRadius, y + Math.sin(theta) * outerRadius);
                this.stroke();
                this.closePath();
            }
    
            this.lineWidth = oldLineWidth;
            this.strokeStyle = oldStrokeStyle;
        }
    
        function generateGradientImgData(width, colorStops) {
            let canvas = document.createElement('canvas');
            canvas.setAttribute('width', width);
            canvas.setAttribute('height', 1);
            let ctx = canvas.getContext('2d'),
                gradient = ctx.createLinearGradient(0, 0, width, 0);
    
            for (let i = 0; i < colorStops.length; i++) {
                gradient.addColorStop(colorStops[i].offset, colorStops[i].color);
            }
    
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, width, 1);
            return ctx.getImageData(0, 0, width, 1);
        }
    })();
    

    This method draws lines from the center of the circle to each pixel along the edge of it. You get a cleaner gradient this way.

    For large line thicknesses, it's still cleaner.

    Its one major drawback is performance. If your radius is very large, the number of lines required to produce a nice circle is about 50 times the radius.

    jsFiddle

    0 讨论(0)
  • 2020-12-01 17:29

    A context strokeStyle can be a gradient:

    // create a gradient
    
    gradient = ctx.createLinearGradient(xStart, yStart, xEnd, yEnd);
    gradient.addColorStop(0.0,"blue");
    gradient.addColorStop(1.0,"purple");
    
    
    // stroke using that gradient
    
    ctx.strokeStyle = gradient;
    

    Example code and a Demo using a gradient strokeStyle: http://jsfiddle.net/m1erickson/w46ps/

    enter image description here

    <!doctype html>
    <html>
    <head>
    <link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
    <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
    <style>
        body{ background-color: ivory; }
        #canvas{border:1px solid red;}
    </style>
    <script>
    $(function(){
    
        var canvas=document.getElementById("canvas");
        var ctx=canvas.getContext("2d");
    
        function drawMultiRadiantCircle(xc, yc, r, radientColors) {
            var partLength = (2 * Math.PI) / radientColors.length;
            var start = 0;
            var gradient = null;
            var startColor = null,
                endColor = null;
    
            for (var i = 0; i < radientColors.length; i++) {
                startColor = radientColors[i];
                endColor = radientColors[(i + 1) % radientColors.length];
    
                // x start / end of the next arc to draw
                var xStart = xc + Math.cos(start) * r;
                var xEnd = xc + Math.cos(start + partLength) * r;
                // y start / end of the next arc to draw
                var yStart = yc + Math.sin(start) * r;
                var yEnd = yc + Math.sin(start + partLength) * r;
    
                ctx.beginPath();
    
                gradient = ctx.createLinearGradient(xStart, yStart, xEnd, yEnd);
                gradient.addColorStop(0, startColor);
                gradient.addColorStop(1.0, endColor);
    
                ctx.strokeStyle = gradient;
                ctx.arc(xc, yc, r, start, start + partLength);
                ctx.lineWidth = 30;
                ctx.stroke();
                ctx.closePath();
    
                start += partLength;
            }
        }
    
        var someColors = [];
        someColors.push('#0F0');
        someColors.push('#0FF');
        someColors.push('#F00');
        someColors.push('#FF0');
        someColors.push('#F0F');
    
        drawMultiRadiantCircle(150, 150, 120, someColors);
    
    }); // end $(function(){});
    </script>
    </head>
    <body>
        <canvas id="canvas" width=300 height=300></canvas>
    </body>
    </html>
    
    0 讨论(0)
  • 2020-12-01 17:51

    I needed this effect, too a few days ago, and I have managed to create a workaround to achieve it.

    What I did was overlay one gradient over the other using something like this:

    var ic = [
          /*0*/{ a:"#FEC331" ,b:"#FB1E24"     ,r1:0   ,r2:1   ,x0:0   ,y0:rd*0.5 ,x1:0  ,y1:-rd},
          /*1*/{ a:"#FEC331" ,b:"#FB1E24"     ,r1:0.5 ,r2:0.5 ,x0:0   ,y0:rd*0.3 ,x1:0  ,y1:-rd},
          /*2*/{ a:"#EA6F2B" ,b:"transparent" ,r1:0   ,r2:1   ,x0:-rd ,y0:0      ,x1:rd ,y1:0  }
      ];
    

    Here's the complete code and demo in JSFiddle:

    https://jsfiddle.net/flamedenise/n9no9Lgw/33/

    Hope it helps.

    0 讨论(0)
提交回复
热议问题