From @timseverien and I.
A Pen by Hugo Giraudel on CodePen.
From @timseverien and I.
A Pen by Hugo Giraudel on CodePen.
<div class="grid grid-1"><span><span></span></span></div> | |
<div class="grid grid-2"><span><span></span></span></div> | |
<div class="grid grid-3"><span><span></span></span></div> |
/** | |
* Little helper to create a circle | |
* @group control-points | |
* @access private | |
* @param {Number} $size - Circle size | |
* @output Dimensions, margin and border-radius | |
*/ | |
@mixin circle($size) { | |
width: $size; | |
height: $size; | |
margin: -($size/2); | |
border-radius: 50%; | |
} | |
/** | |
* Helper to position an item in absolute context | |
* @param {Number} $top (null) - Top offset | |
* @param {Number} $right (null) - Right offset | |
* @param {Number} $bottom (null) - Bottom offset | |
* @param {Number} $left (null) - Left offset | |
* @output `position: absolute` and offsets | |
*/ | |
@mixin absolute($top: null, $right: null, $bottom: null, $left: null) { | |
position: absolute; | |
top: $top; | |
right: $right; | |
bottom: $bottom; | |
left: $left; | |
} | |
/** | |
* Linear interpolation | |
* @author Tim Severien | |
* @param {Number} $a | |
* @param {Number} $b | |
* @param {Number} $p | |
* @return {Number} | |
*/ | |
@function lerp($a, $b, $p) { | |
@return ($b - $a) * $p + $a; | |
} | |
/** | |
* Linear interpolation points | |
* @author Tim Severien | |
* @param {Number} $a | |
* @param {Number} $b | |
* @param {Number} $p | |
* @return {List} | |
*/ | |
@function lerp-point($a, $b, $p) { | |
@return lerp(nth($a, 1), nth($b, 1), $p), lerp(nth($a, 2), nth($b, 2), $p); | |
} | |
/** | |
* Bezier Reduce | |
* @author Tim Severien | |
* @param {List} $points | |
* @param {Number} $p | |
* @return {Number} | |
*/ | |
@function bezier-reduce($points, $p) { | |
@while length($points) > 1 { | |
$tmp: (); | |
@for $i from 1 to length($points) { | |
$tmp: append($tmp, lerp-point(nth($points, $i), nth($points, $i + 1), $p)); | |
} | |
$points: $tmp; | |
} | |
@return nth($points, 1); | |
} | |
/** | |
* Bezier shadow | |
* @param {List} $points - List of points from Bezier | |
* @param {Number} $detail - Number of particles | |
* @output box-shadow | |
* @author Tim Severien | |
*/ | |
@mixin bezier-shadow($points, $detail) { | |
$shadow: (); | |
@for $i from 0 to $detail { | |
$point: bezier-reduce($points, $i / $detail); | |
$shadow: append($shadow, nth($point, 1) nth($point, 2), comma); | |
} | |
box-shadow: $shadow; | |
} | |
/** | |
* Curve wrapper | |
* @access private | |
* @param {Map} $conf - Curve configuration | |
* @requires {mixin} absolute | |
* @output Contexte | |
*/ | |
@mixin draw-curve-wrapper($conf) { | |
@include absolute($top: 0, $right: 0, $bottom: 0, $left: 0); | |
color: map-get($conf, 'color'); | |
} | |
/** | |
* Display dots with `::after` pseudo-element | |
* @access private | |
* @param {Map} $conf - Curve configuration | |
* @requires {mixin} absolute | |
* @requires {mixin} circle | |
* @output Dots | |
*/ | |
@mixin draw-dots($conf) { | |
$args: map-get($conf, 'points'); | |
$size: map-get($conf, 'size'); | |
&::after { | |
content: ''; | |
@include circle(4px); | |
@include absolute($left: 0, $top: 0); | |
@include bezier-shadow(( | |
0 $size, | |
(nth($args, 1) * $size) ((1 - nth($args, 2)) * $size), | |
(nth($args, 3) * $size) ((1 - nth($args, 4)) * $size), | |
$size 0 | |
), map-get($conf, 'detail')); | |
} | |
} | |
/** | |
* Draw control-points as well as lines getting to those | |
* @access private | |
* @param {Map} $conf - Curve configuration | |
* @requires {mixin} absolute | |
* @requires {mixin} circle | |
* @output control-points | |
*/ | |
@mixin draw-control-points($conf) { | |
$args: map-get($conf, 'points'); | |
$size: map-get($conf, 'size'); | |
$color: map-get($conf, 'color'); | |
$x1: nth($args, 1); | |
$y1: nth($args, 2); | |
$x2: nth($args, 3); | |
$y2: nth($args, 4); | |
&::before { | |
content: ''; | |
@include absolute; | |
@include circle(10px); | |
box-shadow: | |
0 $size 0 0 tomato, | |
$size 0 0 0 deepskyblue, | |
($size * $x1) $size - ($size * $y1) 0 0 tomato, | |
($size * $x2) $size - ($size * $y2) 0 0 deepskyblue; | |
} | |
> * { | |
@include absolute($top: 0, $right: 0, $bottom: 0, $left: 0); | |
&::before { | |
@include absolute($left: 0, $bottom: 0); | |
content: ''; | |
width: $x1 * 100%; | |
height: $y1 * 100%; | |
background: linear-gradient( | |
if($x1 == 1, 90deg, 0 - atan($y1 / $x1) * 1rad), | |
transparent calc(50% - 1px), | |
$color calc(50% - 1px), | |
$color calc(50% + 1px), | |
transparent calc(50% + 1px) | |
); | |
} | |
&::after { | |
$v: abs($x2 * 100 - 100); | |
$w: abs($y2 * 100 - 100); | |
@include absolute($top: 0, $right: 0); | |
width: if($v == 0, 1px, $v * 1%); | |
height: if($w == 0, 1px, $w * 1%); | |
content: ''; | |
background: linear-gradient( | |
if($x2 == 1, 90deg, 0 - atan(($y2 - 1) / ($x2 - 1)) * 1rad), | |
transparent calc(50% - 1px), | |
$color calc(50% - 1px), | |
$color calc(50% + 1px), | |
transparent calc(50% + 1px) | |
); | |
} | |
} | |
} | |
/** | |
* Main function, drawing a Bezier function | |
* @access private | |
* @param {Map} $conf - Curve configuration | |
* @output Dots and possibly control-points | |
*/ | |
@mixin draw-curve($conf) { | |
// Print the wrapper | |
@include draw-curve-wrapper($conf); | |
// Print the dots | |
@include draw-dots($conf); | |
// Print the control-points | |
@if map-get($conf, 'control-points') != false { | |
@include draw-control-points($conf); | |
} | |
} | |
/** | |
* Draw coords system | |
* @access private | |
* @param {Map} $conf - Curve configuration | |
* @param {Bool} $display-name (false) - Enable/disable name display | |
*/ | |
@mixin draw-system($conf) { | |
width: map-get($conf, 'size'); | |
height: map-get($conf, 'size'); | |
position: relative; | |
color: map-get($conf, 'color'); | |
border-left: 2px solid; | |
border-bottom: 2px solid; | |
border-top: 1px dashed; | |
border-right: 1px dashed; | |
&::after, | |
&::before { | |
position: absolute; | |
bottom: -1.75em; | |
text-transform: uppercase; | |
font-size: .75em; | |
} | |
@if map-get($conf, 'informations') { | |
@if map-has-key($conf, 'name') { | |
// Display name | |
&::before { | |
content: "#{map-get($conf, 'name')}"; | |
left: 0; | |
} | |
} | |
// Display values | |
&::after { | |
content: "#{map-get($conf, 'points')}"; | |
right: 0; | |
} | |
} | |
// Print the curve | |
> * { | |
@include draw-curve($conf); | |
} | |
} | |
/** | |
* Draw a cubic bezier function. | |
* Also initialize a global variable holding the configuration. | |
* @access public | |
* @param {Number} $x1 - X of first pair | |
* @param {Number} $y1 - Y of first pair | |
* @param {Number} $x2 - X of second paid | |
* @param {Number} $y2 - Y of second pair | |
* @param {Map} $options - Map of options | |
*/ | |
@mixin cubic-bezier($x1, $y1, $x2, $y2, $options: ()) { | |
// Default options | |
$options: map-merge(( | |
// Enable/disable control-points | |
'control-points': true, | |
// Extra informations | |
'informations': true, | |
// Size of the grid | |
'size': 300px, | |
// Color scheme | |
'color': #999, | |
// Points from the curve | |
'points': ($x1, $y1, $x2, $y2), | |
// Number of dots on the curve | |
'detail': 30 | |
), $options); | |
// Start printing things | |
@include draw-system($options); | |
} | |
/** | |
* DEMO * | |
**/ | |
.grid { | |
margin: 1em; | |
float: left; | |
} | |
.grid-1 { | |
@include cubic-bezier(.42, 0, 1, 1, ( | |
'name': 'ease-in' | |
)); | |
} | |
.grid-2 { | |
@include cubic-bezier(.42, 0, .58, 1, ( | |
'name': 'ease-in-out' | |
)); | |
} | |
.grid-3 { | |
@include cubic-bezier(.13, .88, .5, .42); | |
} | |