How to animate a radial-gradient using CSS?

后端 未结 2 1194
挽巷
挽巷 2020-11-27 07:20

I am trying to create a radial-gradient shine affect to a div box and I am unsure whats the best way of doing so. I Have found no resources of achieving what I want to achie

相关标签:
2条回答
  • 2020-11-27 07:46

    You can do the gradient differently and animate the position. The trick is to double the size of the gradient and make the value of color stop half of their actual values so you keep the same visual gradient then you can animate it from left to right.

    It won't look exactly the same as the gradient you defined in the animation due to the calculation of farthest-corner.

    #shine-div {
      height: 30vh;
      width: 60vw;
      margin-right: auto;
      margin-left: auto;
      border-radius: 10px;
      background: radial-gradient(farthest-corner at top, #FFFFFF 0%, #ffb3ff 4%, #ff33ff 12.25%, #800080 31.25%, #b300b3 50%) top right/200% 200%;
      display: flex;
      justify-content: center;
      align-items: center;
      color: white;
      font-weight: bold;
      animation: colorChange 5s infinite alternate;
    }
    
    @keyframes colorChange {
      to {
        background-position:top left;
      }
     }
    <div id="shine-div">
      Shine
    </div>


    To get more closer to your gradients you have to also animate the background-size (see below for calculation details)

    #shine-div {
      height: 30vh;
      width: 60vw;
      margin-right: auto;
      margin-left: auto;
      border-radius: 10px;
      background: radial-gradient(farthest-corner at top, #FFFFFF 0%, #ffb3ff 8%, #ff33ff 24.5%, #800080 62.5%, #b300b3 100%);
      display: flex;
      justify-content: center;
      align-items: center;
      color: white;
      font-weight: bold;
      animation: colorChange 5s infinite alternate linear;
    }
    
    @keyframes colorChange {
      from { /* radial-gradient(farthest-corner at top right, ..) */
        background-position:left top;
        background-size:200% 100%;
      
      }
      49.9% {
        background-position:left top;  
      }
      50% { /* radial-gradient(farthest-corner at top center, ..) */
        background-size:100% 100%;
      }
      50.1% {
        background-position:right top; 
      }
      to { /* radial-gradient(farthest-corner at top left, ..) */
        background-position:right top;
        background-size:200% 100%;
      }
     }
    <div id="shine-div">
      Shine
    </div>

    You can also do the same animation considering pseudo element and transformation to have better performance:

    #shine-div {
      height: 30vh;
      width: 60vw;
      margin-right: auto;
      margin-left: auto;
      border-radius: 10px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: white;
      font-weight: bold;
      overflow:hidden;
      position:relative;
      z-index:0;
    }
    #shine-div:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      width:400%;
      height:200%;
      background: radial-gradient(farthest-corner at top, #FFFFFF 0%, #ffb3ff 4%, #ff33ff 12.25%, #800080 31.25%, #b300b3 50%);
      animation: colorChange 5s infinite alternate linear;
    }
    
    @keyframes colorChange {
      from {
        transform:translateX(-50%);
      }
      50% {
        transform:scaleX(0.75) translateX(-50%)
      }
      to {
        transform:translateX(-25%);
      }
     }
    <div id="shine-div">
      Shine
    </div>



    More in-depth

    To make the answer more generic, I am going to detail how you can animate any kind of gradient from two different position. The main trick is to write the gradient differently to have its definition a constant ( radial-gradient(<constant_definition>) ) and animate the background-position (and the background-size in some cases)

    Let's consider our gradient to be background:radial-gradient(Rh Rv at X Y, color1 p1, color2 p2) where Rh and Ry are respectively the horizontal radius and vertical radius of our ellipse (if both are equal or only one value is used then it's a circle).

    First, we double the size of the gradient. This trick will allow us to easily adjust the position of the gradient using percentage value (explained here: Using percentage values with background-position on a linear-gradient)

    If the radius is defined with pixel values we keep it but if it's defined with percentage value we divide it by 2 since it's relative to the size that he have increased. If both radius are in percentage we can either divide both by 2 or keep them and divide the color stops by 2.

    Second, we remove the at X Y which will bring the gradient in the center thus we need to rectify the position using background-position. It's clear that if the gradient was at 0 0 we need to use background-position:100% 100%

    The green box is our background twice bigger than the element (the black box) and the red circle is our gradient. By adjusting the background position we visually position the gradient at 0 0.

    For any X, Y values we will logically have background-position:calc(100% - X) calc(100% - Y)

    If X,Y are pixel values we can also use background-position: right -X bottom -Y (note that it' -X and not - X, we use the negative value)

    Examples:

    With pixel values

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(20% 100px at 20px 30px,red 30%,blue 60%);"></div>
    <div class="box" style="background:radial-gradient(10% 100px,red 30%,blue 60%) right -20px bottom -30px/200% 200%;"></div>
    <br>
    <div class="box" style="background:radial-gradient(40% 40% at 40px 50px,yellow 30%,blue);"></div>
    <div class="box" style="background:radial-gradient(40% 40%,yellow 15%,blue 50%) right -40px bottom -50px/200% 200%;"></div>
    <div class="box" style="background:radial-gradient(20% 20%,yellow 30%,blue) right -40px bottom -50px/200% 200%;"></div>

    With percentage values

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(20% 100px at 50% 10%,red 30%,blue 60%);"></div>
    <div class="box" style="background:radial-gradient(10% 100px,red 30%,blue 60%) calc(100% - 50%) calc(100% - 10%)/200% 200%;"></div>
    <br>
    <div class="box" style="background:radial-gradient(40% 40% at 30% 70%,yellow 30%,blue);"></div>
    <div class="box" style="background:radial-gradient(40% 40%,yellow 15%,blue 50%) calc(100% - 30%) calc(100% - 70%)/200% 200%;"></div>
    <div class="box" style="background:radial-gradient(20% 20%,yellow 30%,blue) calc(100% - 30%) calc(100% - 70%)/200% 200%;"></div>

    So if we want to animate a gadient from:

    radial-gradient(Rh Rv at X Y, color1 p1, color2 p2)
    

    to

    radial-gradient(Rh Rv at X1 Y2, color1 p1, color2 p2)
    

    we write it differently and we animate the background-position:

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    .first {
      background:radial-gradient(10% 100px,red 30%,blue 60%) calc(100% - 50%) calc(100% - 10%)/200% 200%;
      animation:change1 2s linear infinite alternate;
    }
    .second {
      background:radial-gradient(20% 20%,yellow 30%,blue)right -50px bottom 0/200% 200%;
      animation:change2 2s linear infinite alternate;
    }
    
    @keyframes change1 {
      to {
        background-position:calc(100% + 10%) calc(100% - 80%);
      }
    }
    
    @keyframes change2 {
      to {
        background-position:right -100px bottom -100px;
      }
    }
    <div class="box first" ></div>
    <div class="box second"></div>


    Now let's consider more tricky cases, like our initial example, using farthest-side in order to define the size. We will do the same logic and convert

    radial-gradient(farthest-side at X Y, color1 p1, color2 p2);
    

    to

    radial-gradient(farthest-side, color1 p1, color2 p2) Px Py/Sx Sy no-repeat;
    

    I will explain for one axis (X) and the same apply to the other

    farthest-side define the radius to be the distance from the gradient center to the farthest side of the gradient box (the gradient box is by default the element itself since we didn't define any size). If X is a percentage value then the radius is the max between X and 100% - X and in the transformed gradient the radius will be 50% since we are at the center. So we need to match the first radius with 50%*Sx

    If X is 50% then Sx should be 100% and if X is 0 or 100% then Sx should be 200%.

    The formula is Sx = max(X,100% - X)*2

    The position is easier in this case due to the nature of the gradient where the shape should touch one side

    • If X within [0 50%[ Px should be 100% (right)
    • If X is 50% any value for Px will work since Sx=100%
    • If X within ]50% 100%] Px shoudd be 0% (left)

    Related question: Using percentage values with background-position on a linear-gradient

    Examples:

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(farthest-side at 20% 60%, red 20%, blue 100%, yellow 100%)" ></div>
    <div class="box" style="background:radial-gradient(farthest-side, red 20%, blue 100%, yellow 50%) 100% 0/calc(80%*2) calc(60%*2)"></div>
    <br>
    <div class="box" style='background:radial-gradient(farthest-side at 22% 100%,red 40%, blue 100%,yellow 100%)'></div>
    <div class="box" style="background:radial-gradient(farthest-side,red 40%, blue 100%,yellow 100%) 100% 0/calc(78%*2) calc(100%*2)"></div>

    For the farthest-corner we do exactly the same:

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(farthest-corner at 20% 60%, red 20%, blue 50%, yellow 60%)" ></div>
    <div class="box" style="background:radial-gradient(farthest-corner, red 20%, blue 50%, yellow 60%) 100% 0%/calc(80%*2) calc(60%*2)"></div>
    <br>
    <div class="box" style="background:radial-gradient(farthest-corner at 40% 100%, red 20%, blue 50%, yellow 60%)" ></div>
    <div class="box" style="background:radial-gradient(farthest-corner, red 20%, blue 50%, yellow 60%) 100% 0%/calc(60%*2) calc(100%*2)"></div>

    We can also transform farthest-side (or farthest-corner) to Rh Rv and do the previous calculation but it won't be useful for the animation since we will have two gradient with different radius whereas we need the same gradient.

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(farthest-side at 20% 60%, red 20%, blue 100%, yellow 100%)" ></div>
    <div class="box" style="background:radial-gradient(80% 60% at 20% 60%, red 20%, blue 100%, yellow 100%)" ></div>
    <div class="box" style="background:radial-gradient(80% 60%, red 10%, blue 50%, yellow 50%) 80% 40%/200% 200%"></div>

    If X is a pixel value we have two cases:

    • The element has a fixed width: In this case we can simply convert the pixel value of X as a percentage of the width and we do the same logic as above.
    • The element has a variable width: In this case it would be tricky to convert the gradient (probably impossible) because the shape will change based on the width. When width-X > X we will have a variable radius and when width-X < X we will have a fixed radius. I don't think we can express this using background-size and background-position. Example:

    body {
      margin:0;
      height:100vh;
      background:radial-gradient(farthest-side at 400px 200px,blue 40%,yellow 50%);
    }

    For the closest-side will do the same logic considering Sx=min(X,100% - X)*2 BUT we should add no-repeat and a background-color equal to the last color in the gradient since the size is less than 100%

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(closest-side at 20% 60%, red 20%, blue 100%, yellow 100%)" ></div>
    <div class="box" style="background:radial-gradient(closest-side, red 20%, blue 100%, yellow 100%) 0 100%/calc(20%*2) calc(40%*2)"></div>
    <div class="box" style="background:radial-gradient(closest-side, red 20%, blue 100%, yellow 100%) 0 100%/calc(20%*2) calc(40%*2) no-repeat,yellow"></div>
    <br>
    <div class="box" style='background:radial-gradient(closest-side at 22% 10%,red 40%, blue 100%,yellow 100%)'></div>
    <div class="box" style="background:radial-gradient(closest-side,red 40%, blue 100%,yellow 100%) 0 0/calc(22%*2) calc(10%*2)"></div>
    <div class="box" style="background:radial-gradient(closest-side,red 40%, blue 100%,yellow 100%) 0 0/calc(22%*2) calc(10%*2) no-repeat,yellow"></div>

    We can do the same for closest-corner but we will have some issue due the fact that the gradient can overflow the gradient box.

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(closest-corner at 20% 60%, red 20%, blue 100%, yellow 100%)" ></div>
    <div class="box" style="background:radial-gradient(closest-corner, red 20%, blue 100%, yellow 100%) 0 100%/calc(20%*2) calc(40%*2)"></div>
    <div class="box" style="background:radial-gradient(closest-corner, red 20%, blue 100%, yellow 100%) 0 100%/calc(20%*2) calc(40%*2) no-repeat,yellow"></div>

    To rectify this we can divide the color stop by 2 to make sure we keep the whole gradient inside. Then we make the size twice bigger and we rectify the position

    .box {
      height:150px;
      width:150px;
      border:1px solid;
      display:inline-block;
    }
    <div class="box" style="background:radial-gradient(closest-corner at 20% 60%, red 20%, blue 100%, yellow 100%)" ></div>
    <div class="box" style="background:radial-gradient(closest-corner, red 10%, blue 50%, yellow 50%) -100% 33%/calc(20%*4) calc(40%*4)"></div>
    <div class="box" style="background:radial-gradient(closest-corner, red 10%, blue 50%, yellow 50%) -100% 33%/calc(20%*4) calc(40%*4) no-repeat,yellow"></div>
    <br>
    <div class="box" style='background:radial-gradient(closest-corner at 22% 10%,red 40%, blue 100%,yellow 100%)'></div>
    <div class="box" style="background:radial-gradient(closest-corner,red 20%, blue 50%,yellow 50%) -100% 0%/calc(22%*4) calc(10%*4)"></div>
    <div class="box" style="background:radial-gradient(closest-corner,red 20%, blue 50%,yellow 50%) -164% -18%/calc(22%*4) calc(10%*4) no-repeat,yellow"></div>


    Even without animation, the syntax of the gradient without the at X Y is more supported. Some browser like Safari doesn't support the at (How to make radial gradients work in Safari?)

    0 讨论(0)
  • 2020-11-27 08:09

    SVG solution

    The author did not ask for a solution to his problem using SVG. But it will probably be useful to solve one issue in several ways.
    Gradient attribute values were taken from the @Temani Afif response.
    The SVG radial gradient formula for this question:

    <radialGradient id="radGrad"  fx="0%" fy="5%" r="200%">
         <stop offset="0%" stop-color ="#FFFFFF" />
          <stop offset="4%" stop-color ="#ffb3ff" />
           <stop offset="12.25%" stop-color ="#ff33ff" />
            <stop offset="31.25%" stop-color ="#800080" />
              <stop offset="50%" stop-color ="#b300b3" /> 
    
       </radialGradient>
    

    To animate the gradient, you can use any attribute included in the formula.
    The examples below will use the attributes fx andfy

    • Animation of horizontal gradient movement

    Animation starts after clicking on a rectangle

    svg {
     width:50%;
     height:50%;
     }
     .txt {
     font-family:sans-serif;
     font-size:28px;
     font-weight:bold;
     text-anchor:middle;
     fill:#FFDD00;
      }
    <div id="shine-div">
       <svg xmlns="http://www.w3.org/2000/svg" 
        xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 100">
       <defs>
      <radialGradient id="radGrad"  fx="0%" fy="0%" r="200%">
          <stop offset="0%" stop-color ="#FFFFFF" />
    	    <stop offset="4%" stop-color ="#ffb3ff" />
    	    <stop offset="12.25%" stop-color ="#ff33ff" />
    	    <stop offset="31.25%" stop-color ="#800080" />
    	    <stop offset="50%" stop-color ="#b300b3" /> 		 
      </radialGradient>
       </defs> 
        <g id="gr1" > 
          <rect id="rect1" fill="url(#radGrad)" x="5%" y="5%" width="95%" height="95%" rx="10%"/> 
           <text class="txt" x="50%" y="60%"> Sun shine </text>
    	</g>  
        <animate xlink:href="#radGrad"
    	  attributeName="fx"
    	  dur="3s"begin="gr1.click"
    	  values="0%;100%;0%"
    	  
    	  repeatCount="1"
    	  restart="whenNotActive" />
      </svg>
    </div>

    • An animation of the vertical gradient movement.

    svg {
     width:50%;
     height:50%;
     }
     .txt {
     font-family:sans-serif;
     font-size:28px;
     font-weight:bold;
     text-anchor:middle;
     fill:#FFDD00;
      }
    <div id="shine-div">
       <svg xmlns="http://www.w3.org/2000/svg" 
        xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 100">
       <defs>
      <radialGradient id="radGrad"  fx="48%" fy="0%" r="200%">
            <stop offset="0%" stop-color ="#FFFFFF" />
    	    <stop offset="4%" stop-color ="#ffb3ff" />
    	    <stop offset="12.25%" stop-color ="#ff33ff" />
    	    <stop offset="31.25%" stop-color ="#800080" />
    	    <stop offset="50%" stop-color ="#b300b3" /> 		 
      </radialGradient>
       </defs> 
        <g id="gr1" > 
          <rect id="rect1" fill="url(#radGrad)" x="5%" y="5%" width="95%" height="95%" rx="10%"/> 
           <text class="txt" x="50%" y="60%"> Sun shine </text>
    	</g>  
        <animate xlink:href="#radGrad"
    	  attributeName="fy"
    	  dur="2s"begin="gr1.click"
    	  values="0%;50%;50%;100%;50%;50%;0%"
    	  keyTimes="0;0.1;0.5;0.6;0.7;0.9;1"
    	  repeatCount="1"
    	  restart="whenNotActive" />
      </svg>
    </div>

    • Moving the gradient diagonally

    Two attributes are simultaneously animated: fx andfy

    svg {
     width:50%;
     height:50%;
     }
     .txt {
     font-family:sans-serif;
     font-size:28px;
     font-weight:bold;
     text-anchor:middle;
     fill:#FFDD00;
      }
    <div id="shine-div">
       <svg xmlns="http://www.w3.org/2000/svg" 
        xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 100">
       <defs>
      <radialGradient id="radGrad"  fx="0%" fy="0%" r="200%">
            <stop offset="0%" stop-color ="#FFFFFF" />
    	    <stop offset="4%" stop-color ="#ffb3ff" />
    	    <stop offset="12.25%" stop-color ="#ff33ff" />
    	    <stop offset="31.25%" stop-color ="#800080" />
    	    <stop offset="50%" stop-color ="#b300b3" /> 		 
      </radialGradient>
       </defs> 
        <g id="gr1" > 
          <rect id="rect1" fill="url(#radGrad)" x="5%" y="5%" width="95%" height="95%" rx="10%"/> 
           <text class="txt" x="50%" y="60%"> Sun shine </text>
    	</g>  
        <animate xlink:href="#radGrad"
    	  attributeName="fy"
    	  dur="2s"begin="gr1.click"
    	  values="0%;50%;50%;100%;0%"
    	  keyTimes="0;0.1;0.5;0.9;1"
    	  repeatCount="1"
    	  restart="whenNotActive" />
    	  
    	     <animate xlink:href="#radGrad"
    			  attributeName="fx"
    			  dur="2s"begin="gr1.click"
    			  values="0%;50%;50%;100%;0%"
    			  keyTimes="0;0.1;0.5;0.9;1"
    			  repeatCount="1"
    			  restart="whenNotActive" />
      </svg>
    </div>

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