- const length = 8; // SYNC WITH CSS

ul.list
  - for ( let i = 0; i < length; i++ )
    li.item
      .bubble
View Compiled
@import url('https://fonts.googleapis.com/css?family=Quicksand');

// Settings

$length: 8; // SYNC WITH HTML
$limit: 99;
$space: 3.5vw;
$time: 1s;

// Colors

$color-background: #1c0c2a;
$color-border: #343b99;
$color-color: #61bdf6;
$color-hover: #9affdc;

// Animations

$animation-scale: linear infinite;
$animation-radius: ease-in-out infinite;
$animation-translate: translate-3D linear infinite;
$animation-bubble-sort: ease-in-out forwards 1s;

// Functions

@function digits ( $number )
{
  @return str-length( quote( #{ $number } ) );
}

@function to-fixed ( $number, $digits, $method: round )
{
  $n: 1;
  
  @for $i from 1 through $digits
  {
    $n: $n * 10;
  }
  
  @return call( get-function( $method ), $number * $n ) / $n;
}

@function percents ( $steps, $digits: 2 )
{
  @return to-fixed( 100 / ( $steps + 1 ), $digits ) * 1%;
}

@function distance ( $index, $size: $size, $space: $space )
{
  @return ( $index - 1 ) * $size + $index * $space;
}

@function random-list ( $length, $limit )
{
  $list: ();
  
  @while length( $list ) < $length
  {
    $number: random( $limit );
    
    @if not index( $list, $number )
    {
      $list: append( $list, $number );
    }
  }
  
  @return $list;
}

@function map-steps ( $map, $key, $step, $value )
{
  $steps: map-get( $map, $key );
  
  $step-value: $step $value;
    
  @return map-merge( $map, ( $key: append( if( $steps, $steps, () ), $step-value, comma ) ) );
}

@function bubble-sort ( $list )
{
  $length: length( $list ) - 1;
  $map: ();
  $steps: 0;
  $swapped: true;
  
  @while $swapped
  {
    $swapped: false;
    
    @for $j from 1 through $length
    {
      $index: $j + 1;
      $prev: nth( $list, $j );
      $next: nth( $list, $index );

      @if $prev > $next
      {
        $steps: $steps + 1;
        $swapped: if( $swapped, $swapped, true );
        
        $list: set-nth( $list, $j, $next );
        $list: set-nth( $list, $index, $prev );

        $map: map-steps( $map, $prev, $steps, 1 );
        $map: map-steps( $map, $next, $steps, -1 );
      }
    }
  }
  
  @return $map $steps;
}

// Variables

$size: to-fixed( ( 100vw - ( $length + 1 ) * $space ) / $length, 2 );
$shadow: 0 0 $size * 0.05 0 rgba( $color-hover, .25 );

$radius-map: ( 
  1: 40% 50% 50% 50%, 
  2: 50% 40% 50% 50%, 
  3: 50% 50% 40% 50%, 
  4: 50% 50% 50% 40%
);

// Mixins

@mixin size ( $w: null, $h: null )
{
  width: $w;
  height: $h;
}

@mixin box-size ( $s )
{
  width: $s;
  height: $s;
}

@mixin keyframes-radius ( $name, $steps, $map )
{
  $percent: percents( $steps );
  
  @keyframes #{ $name }
  {
    @for $step from 1 through $steps
    {
      $keys: map-keys( $map );

      $key: nth( $keys, random( length( $keys ) ) );

      $value: map-get( $map, $key );

      $map: map-remove( $map, $key );
 
      #{ $percent * $step } { border-radius: $value }
    }
  }
}

@mixin keyframes-scale ( $name, $steps, $base: .85 )
{
  $percent: percents( $steps );
  
  $random: random( 2 );
  
  @keyframes #{ $name }
  {
    @for $step from 1 through $steps
    {
      $mod2: ( $random + $step ) % 2 == 0;
      
      $scale: to-fixed( random() / 10 + $base, 3 );
      
      $x: if( $mod2, $scale, 1 );
      
      $y: if( $mod2, 1, $scale );
      
      #{ $percent * $step } { transform: scale3d( $x, $y, 1 ) }
    }
  }
}

@mixin keyframes-bubble-sort ( $name, $index, $key, $map, $steps )
{
  $list: map-get( $map, $key );
  $length: length( $list );
  $percent: percents( $steps );
  $prev: nth( nth( $list, 1 ), 1 ) * $percent;
    
  @keyframes #{ $name }
  {
    0%, #{ $prev } { left: distance( $index ) }
    
    @for $i from 1 through $length
    {     
      $next: if( $i == $length, 100%, nth( nth( $list, $i + 1 ), 1 ) * $percent );

      $index: $index + nth( nth( $list, $i ), 2 );
      
      #{ $prev + $percent / 2 }, #{ $next } { left: distance( $index ) }

      $prev: $next;
    }
  }
}

@mixin bubble-sort ( $length, $limit )
{
  $list: random-list( $length, $limit );
  $bubbles: bubble-sort( $list );
  $sorted-list: nth( $bubbles, 1 );
  
  --time-sort: #{ nth( $bubbles, 2 ) * $time };
  
  @for $index from 1 through $length
  {
    $name-radius: radius-#{ $index };
    $name-scale: scale-#{ $index };
    $content: nth( $list, $index );
    
    &:nth-of-type( #{ $index } )
    {   
      @if map-get( nth( $bubbles, 1 ), $content ) != null
      {
        $name-sorting: sorting-#{ $index };
        
        @include keyframes-bubble-sort( $name-sorting, $index, $content, $bubbles... );
        
        animation-name: $name-sorting;
      }
      
      @include keyframes-radius( $name-radius, 4, $radius-map );      
      @include keyframes-scale( $name-scale, 4 );

      --t-y: #{ if( random( 2 ) == 1, 1, -1 ) * ( 10 + random( 20 ) ) };
      --degrees: #{ random( 360 ) - 1 };
      --time-bubble: #{ random( 5 ) + 5s };
      
      left: distance( $index );
              
      .bubble
      {
        &::before { animation-name: $name-radius, $name-scale }
      
        &::after { content: '#{ $content }' }
      }
    }
  }
}

// Extends

%flex-center
{
  display: flex;
  align-items: center;
  justify-content: center;
}

%full-size
{
  @include box-size( 100% );
}

/* RESET */

*,
*::before,
*::after
{
  outline: 0;
  margin: 0;
  border: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Quicksand', sans-serif;
}

// Layout

html
{
  @include size( 100vw, 100vh );
}

body
{
  @extend %full-size, %flex-center;
  background: $color-background;
  overflow: hidden;
}

.list
{
  list-style: none;
  display: flex;
  align-items: center;
}

.item
{
  @extend %flex-center;
  @include box-size( $size );
  animation: var( --time-sort ) $animation-bubble-sort;
  position: absolute;
   
  @include bubble-sort( $length, $limit );
}

.bubble
{
  animation: $animation-translate;
  color: $color-color;
  &::before
  {       
    content: '';
    animation: $animation-radius, $animation-scale;
    box-shadow: $shadow inset, $shadow;
    background: 
      radial-gradient( $color-background 50%, rgba( $color-background, 0 ) ),
      linear-gradient( calc( #{ var( --degrees ) } * 1deg ), $color-color, rgba( $color-color, 0 ) 75% ),
      linear-gradient( calc( ( ( #{ var( --degrees ) } + 120 ) % 360 ) * 1deg ), $color-border, rgba( $color-border, 0 ) 75% ),
      linear-gradient( calc( ( ( #{ var( --degrees ) } + 240 ) % 360 ) * 1deg ), $color-background, rgba( $color-background, 0 ) 75% );
  }
  
  &::after
  {
    color: $color-color;
    font-size: $size / ( digits( $limit ) + .5 );  
  }
   
  &,
  &::before,
  &::after
  {
    @extend %full-size, %flex-center;
    animation-duration: var( --time-bubble );
    border-radius: 50%;
    position: absolute;
  }
}

@keyframes translate-3D
{
  25% { transform: translate3d( 0, calc( #{ var( --t-y ) } * 1% ), 0 ) }
  75% { transform: translate3d( 0, calc( #{ var( --t-y ) } * -1% ), 0 ) }
}
View Compiled
// Update CSS Settings

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.