简体   繁体   中英

Creating a CSS linear gradient based on two points relative to a rectangle

I am trying to recreate the gradient tool in Sketch. The tool in Sketch is using two points with different colors to define a gradient:

渐变工具在 Sketch 中的外观示例

I want the output to be in the form of a CSS linear gradient value. The way a CSS linear gradient is constructed is an angle and x number of color stops with a color and a percentage defined: https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient

So I want to convert two points relative to the rectangle in which the gradient should be rendered to the CSS format (two parameters with the correct percentage).

Any ideas on how to approach this problem?

There is no generic formula but you have to do some manipulation/calculation in order to find the degree of the gradient and also background-size / background-position of the gradient.

Let's start with an easy example:

在此处输入图片说明

Here we have a gradient with 180deg (or 0deg ). The starting point is 50px on the top and the end point is 100px on the bottom. Considering this, we will have the below gradient:

 .box { width: 200px; height: 100px; border: 1px solid; margin: 20px; background-image: linear-gradient(180deg, white, black); background-size: 100% calc(100% + 50px + 100px); background-position: 0 -50px; background-repeat: no-repeat; }
 <div class="box"></div>

As you can see, the total size will be 100% + 150px and we will have an offset of -50px for the position. We can also have an offset considering the 100px and it will be 100% + 100px :

 .box { width:200px; height:100px; border:1px solid; margin:20px; background-image:linear-gradient(180deg,white,black); background-size:100% calc(100% + 50px + 100px); background-position:0 calc(100% + 100px); background-repeat:no-repeat; }
 <div class="box"> </div>

Here is another example:

在此处输入图片说明

In this case, the points are inside so we simply need to adjust color stops inside the gradient:

 .box { width: 200px; height: 100px; border: 1px solid; margin: 20px; background-image: linear-gradient(90deg, white 50px, black calc(100% - 50px)); }
 <div class="box"></div>

Here is mix where we have an outside and inside point:

在此处输入图片说明

 .box { width: 200px; height: 100px; border: 1px solid; margin: 20px; background-image: linear-gradient(90deg, white 50px, black); background-size: calc(100% + 100px) 100%; background-position: 0 0; background-repeat: no-repeat; }
 <div class="box"></div>


As you can see, it's somehow easy when it comes to perpendicular direction. We simply need to find the size of gradient, its position and the color stops inside the gradient.

Of course, it's more tricky when it comes to other values of angle.

Here is an illustration of one example:

在此处输入图片说明

You gradient is defined by the orange line. The first step is to draw the line of the gradient according to the documentation , this line will be parallel to your line. After drawing this line, you will have the angle of the gradient.

After that, we do a projection of your line and you will have your color stops . The needed values are shown with a green color.

Our gradient will look like this:

background-image:linear-gradient(Xdeg,white Apx,black calc(100% - Bpx));

In this case, I considered an example where we only have inside points but it can become more complex if the projection of the orange line will lead to outside points (like the first example) and in this case we need to consider increasing the background-size on both directions and this is also a bit tricky.

在此处输入图片说明

As you can see we have a point outside defined by the distance B from the gradient point. We have build a rectangle triangle in order to find how to increase the background-size .

Our gradient will look like this:

background-image:linear-gradient(Xdeg,white Apx,black);
background-size:calc(100% + w) calc(100% + h);
background-position:0 0;

Update

Another approach in case you don't want to play with background-size / background-position is to convert the gradient to use inside points. Of course, this approach is useful only when the points are outside and the idea is to find the closest inside points that will allow us to obtain the same gradient.

Let's retake the first example. In that example we have the first point at 50px on the top and logically the closest inside point is the one at 0px (same logic with the other point). So we simply need to find what is color of the new points and use them.

 .box { width: 200px; height: 100px; border: 1px solid; margin: 20px; } .old { background-image: linear-gradient(180deg, white, black); background-size: 100% calc(100% + 50px + 100px); background-position: 0 -50px; background-repeat: no-repeat; } .new { background-image:linear-gradient(180deg,rgb(203, 203, 203),rgba(103, 103, 103)); }
 <div class="box old"></div> <div class="box new"></div>

In this particular example, the calculation is easy because the size of the initial gradient was 250px and between the white ( 255,255,255 ) and the black( 0,0,0 ) we have 255 values (almost 250) so we have somehow removed 50 to find the first color and added 100 to find the last one.

Let's take the same gradient but with different colors: purple ( 128,0,128 ) and orange ( 255,165,0 ). the size of the gradient is 250px so the first offset ( 50px ) is 20% of the size and the second offset ( 100px ) is 40% of the size. We use those percentage to find the new colors.

For the red we have 128 and 255 so the difference is 127 and 20% of it is 25.4 (and 40% is 50.4 ) thus the first point will have 153.4 (128 + 25.4) and the last point will have 204.2 (255 - 50.4) . We do the same calculation for the green and blue and we obtain the following gradient:

 .box { width: 200px; height: 100px; border: 1px solid; margin: 20px; } .old { background-image: linear-gradient(180deg, purple, orange); background-size: 100% calc(100% + 50px + 100px); background-position: 0 -50px; background-repeat: no-repeat; } .new { background-image:linear-gradient(180deg,rgb(153, 33, 102),rgba(204, 99, 51)); }
 <div class="box old"></div> <div class="box new"></div>

This was the way I solved it!

If you look at the GIF I have attached which illustrates the points I am using in my calculations. The red line is the gradient line in the center of the rectangle and the black points are the start and end points of the gradient line. The two other points (black and white) is the user controlled points that the user can drag around any way he likes. The two red dots are the closest position on the line relative to each user controlled point (perpendicular line points, p1 and p2).

I get the distance between the perpendicular line points and the gradient line start and end points. And then to calculate the percent value needed for the CSS linear-gradient value, I add the two distances, dived them by the gradient line length and multiply the value by 100.

ax = ((p1.x - gradientLine.point1.x) * (gradientLine.length / 2)) / (gradientLine.point2.x - gradientLine.point1.x);
ay = ((p1.y - gradientLine.point1.y) * (gradientLine.length / 2)) / (gradientLine.point2.y - gradientLine.point1.y);

percentValue = (((ax + ay) / line.length) * 100);

To get the value for the second parameter in the linear-gradient value I just do kind of the same thing, except I subtract 100 with the calculate value.

ax = ((p2.x - gradientLine.point2.x) * (gradientLine.length / 2)) / (gradientLine.point1.x - gradientLine.point2.x);
ay = ((p2.y - gradientLine.point2.y) * (gradientLine.length / 2)) / (gradientLine.point1.y - gradientLine.point2.y);
percentValue = 100 - ((((ax + ay) / gradientLine.length) * 100));

This way I get two percent values and can easily construct my CSS linear-gradient value consisting off the angle of the two user controlled points plus the two percent values I calculate:

background: linear-gradient([angle]deg, black [percentValue1]%, white [percentValue2]%)

渐变工具

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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