parse css gradient rule with Javascript regex

前端 未结 3 1956
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-18 12:23

in my css file I have gradient rule, like this:

background-image:linear-gradient(to right, #FF0000 0%, #00FF00 20px, rgb(0, 0, 255) 100%);

3条回答
  •  攒了一身酷
    2021-01-18 13:12

    This parser from Rafael Caricio seems to work well, handling both linear and radial gradients.

    Tested successful on the gradients listed below, most of which came from the wonderful solution from @DeanTaylor. The only problem with Dean's solution is the inability to handle radial gradients.

    One gradient the parser chokes on is: radial-gradient(at 57% 50%, rgb(102, 126, 234) 0%, rgb(118, 75, 162) 100%)

    Tested Gradients:

    • linear-gradient(to right bottom, #FF0000 0%, #00FF00 20px, rgb(0, 0, 255) 100%)
    • linear-gradient(to right bottom, rgba(255, 0, 0, .1) 0%, rgba(0, 255, 0, 0.9) 20px)
    • radial-gradient(rgb(102, 126, 234), rgb(118, 75, 162))
    • linear-gradient(#FF0000 0%, #00FF00 20px, rgb(0, 0, 255) 100%)
    • linear-gradient(45deg, red, blue)
    • linear-gradient(135deg, orange, orange 60%, cyan)
    • linear-gradient(to right, red 20%, orange 20% 40%, yellow 40% 60%, green 60% 80%, blue 80%)
    • radial-gradient(rgb(102, 126, 234), rgb(118, 75, 162))
    • radial-gradient(circle at 100%, #333, #333 50%, #eee 75%, #333 75%)
    • radial-gradient(ellipse farthest-side at 16% 35%, #ff0000 0%, #00ff00 80%)
    • radial-gradient(circle farthest-side at 28% 50%, #ff0000 0%, #00ff00 80%)
    • radial-gradient(circle farthest-corner at 28% 50%, #ff0000 0%, #00ff00 80%)

    Code:

    // Copyright (c) 2014 Rafael Caricio. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    var GradientParser = (GradientParser || {});
    
    GradientParser.parse = (function() {
    
      var tokens = {
        linearGradient: /^(\-(webkit|o|ms|moz)\-)?(linear\-gradient)/i,
        repeatingLinearGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-linear\-gradient)/i,
        radialGradient: /^(\-(webkit|o|ms|moz)\-)?(radial\-gradient)/i,
        repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
        sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
        extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,
        positionKeywords: /^(left|center|right|top|bottom)/i,
        pixelValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))px/,
        percentageValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))\%/,
        emValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))em/,
        angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
        startCall: /^\(/,
        endCall: /^\)/,
        comma: /^,/,
        hexColor: /^\#([0-9a-fA-F]+)/,
        literalColor: /^([a-zA-Z]+)/,
        rgbColor: /^rgb/i,
        rgbaColor: /^rgba/i,
        number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
      };
    
      var input = '';
    
      function error(msg) {
        var err = new Error(input + ': ' + msg);
        err.source = input;
        throw err;
      }
    
      function getAST() {
        var ast = matchListDefinitions();
    
        if (input.length > 0) {
          error('Invalid input not EOF');
        }
    
        return ast;
      }
    
      function matchListDefinitions() {
        return matchListing(matchDefinition);
      }
    
      function matchDefinition() {
        return matchGradient(
                'linear-gradient',
                tokens.linearGradient,
                matchLinearOrientation) ||
    
              matchGradient(
                'repeating-linear-gradient',
                tokens.repeatingLinearGradient,
                matchLinearOrientation) ||
    
              matchGradient(
                'radial-gradient',
                tokens.radialGradient,
                matchListRadialOrientations) ||
    
              matchGradient(
                'repeating-radial-gradient',
                tokens.repeatingRadialGradient,
                matchListRadialOrientations);
      }
    
      function matchGradient(gradientType, pattern, orientationMatcher) {
        return matchCall(pattern, function(captures) {
    
          var orientation = orientationMatcher();
          if (orientation) {
            if (!scan(tokens.comma)) {
              error('Missing comma before color stops');
            }
          }
    
          return {
            type: gradientType,
            orientation: orientation,
            colorStops: matchListing(matchColorStop)
          };
        });
      }
    
      function matchCall(pattern, callback) {
        var captures = scan(pattern);
    
        if (captures) {
          if (!scan(tokens.startCall)) {
            error('Missing (');
          }
    
          result = callback(captures);
    
          if (!scan(tokens.endCall)) {
            error('Missing )');
          }
    
          return result;
        }
      }
    
      function matchLinearOrientation() {
        return matchSideOrCorner() ||
          matchAngle();
      }
    
      function matchSideOrCorner() {
        return match('directional', tokens.sideOrCorner, 1);
      }
    
      function matchAngle() {
        return match('angular', tokens.angleValue, 1);
      }
    
      function matchListRadialOrientations() {
        var radialOrientations,
            radialOrientation = matchRadialOrientation(),
            lookaheadCache;
    
        if (radialOrientation) {
          radialOrientations = [];
          radialOrientations.push(radialOrientation);
    
          lookaheadCache = input;
          if (scan(tokens.comma)) {
            radialOrientation = matchRadialOrientation();
            if (radialOrientation) {
              radialOrientations.push(radialOrientation);
            } else {
              input = lookaheadCache;
            }
          }
        }
    
        return radialOrientations;
      }
    
      function matchRadialOrientation() {
        var radialType = matchCircle() ||
          matchEllipse();
    
        if (radialType) {
          radialType.at = matchAtPosition();
        } else {
          var extent = matchExtentKeyword();
          if (extent) {
            radialType = extent;
            var positionAt = matchAtPosition();
            if (positionAt) {
              radialType.at = positionAt;
            }
          } else {
            var defaultPosition = matchPositioning();
            if (defaultPosition) {
              radialType = {
                type: 'default-radial',
                at: defaultPosition
              };
            }
          }
        }
    
        return radialType;
      }
    
      function matchCircle() {
        var circle = match('shape', /^(circle)/i, 0);
    
        if (circle) {
          circle.style = matchLength() || matchExtentKeyword();
        }
    
        return circle;
      }
    
      function matchEllipse() {
        var ellipse = match('shape', /^(ellipse)/i, 0);
    
        if (ellipse) {
          ellipse.style =  matchDistance() || matchExtentKeyword();
        }
    
        return ellipse;
      }
    
      function matchExtentKeyword() {
        return match('extent-keyword', tokens.extentKeywords, 1);
      }
    
      function matchAtPosition() {
        if (match('position', /^at/, 0)) {
          var positioning = matchPositioning();
    
          if (!positioning) {
            error('Missing positioning value');
          }
    
          return positioning;
        }
      }
    
      function matchPositioning() {
        var location = matchCoordinates();
    
        if (location.x || location.y) {
          return {
            type: 'position',
            value: location
          };
        }
      }
    
      function matchCoordinates() {
        return {
          x: matchDistance(),
          y: matchDistance()
        };
      }
    
      function matchListing(matcher) {
        var captures = matcher(),
          result = [];
    
        if (captures) {
          result.push(captures);
          while (scan(tokens.comma)) {
            captures = matcher();
            if (captures) {
              result.push(captures);
            } else {
              error('One extra comma');
            }
          }
        }
    
        return result;
      }
    
      function matchColorStop() {
        var color = matchColor();
    
        if (!color) {
          error('Expected color definition');
        }
    
        color.length = matchDistance();
        return color;
      }
    
      function matchColor() {
        return matchHexColor() ||
          matchRGBAColor() ||
          matchRGBColor() ||
          matchLiteralColor();
      }
    
      function matchLiteralColor() {
        return match('literal', tokens.literalColor, 0);
      }
    
      function matchHexColor() {
        return match('hex', tokens.hexColor, 1);
      }
    
      function matchRGBColor() {
        return matchCall(tokens.rgbColor, function() {
          return  {
            type: 'rgb',
            value: matchListing(matchNumber)
          };
        });
      }
    
      function matchRGBAColor() {
        return matchCall(tokens.rgbaColor, function() {
          return  {
            type: 'rgba',
            value: matchListing(matchNumber)
          };
        });
      }
    
      function matchNumber() {
        return scan(tokens.number)[1];
      }
    
      function matchDistance() {
        return match('%', tokens.percentageValue, 1) ||
          matchPositionKeyword() ||
          matchLength();
      }
    
      function matchPositionKeyword() {
        return match('position-keyword', tokens.positionKeywords, 1);
      }
    
      function matchLength() {
        return match('px', tokens.pixelValue, 1) ||
          match('em', tokens.emValue, 1);
      }
    
      function match(type, pattern, captureIndex) {
        var captures = scan(pattern);
        if (captures) {
          return {
            type: type,
            value: captures[captureIndex]
          };
        }
      }
    
      function scan(regexp) {
        var captures,
            blankCaptures;
    
        blankCaptures = /^[\n\r\t\s]+/.exec(input);
        if (blankCaptures) {
            consume(blankCaptures[0].length);
        }
    
        captures = regexp.exec(input);
        if (captures) {
            consume(captures[0].length);
        }
    
        return captures;
      }
    
      function consume(size) {
        input = input.substr(size);
      }
    
      return function(code) {
        input = code.toString();
        return getAST();
      };
    })();
    

提交回复
热议问题