ScalaCSS. Is it time for using it?

ScalaCSS. Is it time for using it?

ScalaCSS. Is it time for using it?

I decided to go further with my Slider and to find out whether my styles can be generated by Scala.

First of all, I added ScalaCSS library and an extension for Scalajs-react to my dependencies:


libraryDependencies += "com.github.japgolly.scalacss" %%% "core" % "0.5.1"

libraryDependencies += "com.github.japgolly.scalacss" %%% "ext-react" % "0.5.1"

ScalaCSS has two ways of making styles:

  1.  To make standalone stylesheets (like SASS or LESS)
  2.  To add css-styles directly to the page by a compiled javascript

 

Standalone stylesheets

As it was written in the ScalaCSS manual:

Produces static CSS for external consumption.

Like SCSS and LESS.

There is only one page about Standalone stylesheets  in the whole manual, and I didn’t find any usage example (only the test). But I suppose, that this option is made to be used for generating CSS in run time and for including it in HTML. I didn’t find any sign, that someone can use it to generate CSS in compile time. That is very different from LESS or SCSS.

I decided to try the “Inline” approach in my example.

 

 

Inline stylesheets

The authors of this library suggest very interesting approach: if you need a style for an element, and, may be, for its children, you should specify a special instance. In run time (again!) javascript generates a class for this instance and CSS for this class.

OK! Let’s begin!

First of all we need an object with dsl inside it:


import scalacss.Defaults._

object MyStyles extends StyleSheet.Inline {
  import dsl._

//We can add our styles

}

Let’s do something simple:


    .slider {
      font-family: sans-serif;
      width: 100%;
      height: 100vh;
      position: relative;
      overflow: hidden;
    }

It’s an equivalent to:


val slider = style(
    font := "sans-serif",
    width(100.%%),
    height(100.vh),
    position.relative,
    overflow.hidden
)

To say truth, it’s not an equivalent, because we don’t know for certain, what class name will be generated. In this example it is “CSS-slider”.

To apply this style to the element:


  <.div(

     CSS.slider,
     //other stuff
   )

But what if we need to specify a style of the element without any class? For instance:


    body {
      margin: 0;
      padding: 0;
    }

We can do it by using unsafeRoot:


style(unsafeRoot("body")(
    margin.`0`,
    padding.`0`
  )
)

As I understand, this style will be applied automatically. By the way, I didn’t find any reference of unsafeRoot in the manual.

OK, let’s take a look at the next part of code:


val slide = style(
    width(100.%%),
    height(100.vh),
    transition := "all 1s ease-in-out",
    transform := "scale(1.1)",
    position.absolute,
    top.`0`,
    left.`0`,
    minHeight(200.px),
    backgroundSize := "cover",
    backgroundPosition := "center",
    zIndex(1),
    opacity(0),
    &.attr("data-active", "true") (
      transform := "scale(1)",
      zIndex(100),
      opacity(1)
    )
)

It’s a style for a slide element. This code gives us following  CSS:


.CSS-slide {
  width: 100%;
  height: 100vh;
  -o-transition: all 1s ease-in-out;
  -webkit-transition: all 1s ease-in-out;
  -moz-transition: all 1s ease-in-out;
  transition: all 1s ease-in-out;
  -moz-transform: scale(1.1);
  -webkit-transform: scale(1.1);
  -o-transform: scale(1.1);
  transform: scale(1.1);
  position: absolute;
  top: 0;
  left: 0;
  min-height: 200px;
  background-size: cover;
  background-position: center;
  z-index: 1;
  opacity: 0;
}

.CSS-slide[data-active="true"] {
  -moz-transform: scale(1);
  -webkit-transform: scale(1);
  -o-transform: scale(1);
  transform: scale(1);
  z-index: 100;
  opacity: 1;
}

First we can notice, that browsers support was automatically added for ‘transform’ and ‘transition’ css properties. That’s good. But it would be better to leave an option to choose. What about me, I don’t use ‘-o-‘ properties at all, and use ‘-moz-‘ prefix only with ‘box-sizing’ css property.

ScalaCSS uses ‘&’ to create pseudo selectors. In our case, the line &.attr("data-active", "true") becomes .CSS-slide[data-active="true"]. It’s very  similar to LESS!

But what about syntax?  Look at these 3 lines of our code:


transform := "scale(1.1)",
position.absolute,
top.`0`,

Every line has  different syntax! It’s not good. It’s very uncomfortable.  Compare it with:


transform: scale(1.1);
position: absolute;
top: 0;

When I have typed a property name in my editor, I don’t want to guess every time, what to do then.

Let’s go further.

Look at this css code:


    .text {
      position: absolute;
      top: 20px;
      left: 20px;
      background: rgba(0, 0, 0, 0.6);
    }
    
    .text a,
    .text a:link {
      transition: all 1s ease-in-out;
      color: white;
      text-decoration: none;
      text-transform: uppercase;
      font-size: 32px;
      padding: 20px;
      display: block;
      cursor: pointer;
    }
    
    .text a:hover,
    .text a:link:hover {
      background: rgba(20, 20, 20, 0.4);
      color: #c8c8c8;
    }

We have two problems now:

  1. We need to define a style of an element with its children
  2. We have a sequences of selectors: .text a, .text a:link and .text a:hover, .text a:link:hover

Fortunately, both problems are solvable.

First problem can be solved by using unsafeChild. For solving the second one we have mixin.


val text_a = mixin(
    color.white,
    transition := "all 1s ease-in-out",
    textDecorationLine.none,
    textTransform.uppercase,
    fontSize(32.px),
    padding(20.px),
    display.block,
    cursor.pointer,
    &.hover(
      backgroundColor(c"rgba(20,20,20,0.4)"),
      color(c"#c8c8c8")
    )
  )



  val text = style(
    position.absolute,
    top(20.px),
    left(20.px),
    backgroundColor(c"rgba(0,0,0,0.6)"),

    unsafeChild("a")(
      text_a
    ),

    unsafeChild("a:link")(
      text_a
    )
  )

The generated css is much bigger than the original css:



.CSS-text {
  position: absolute;
  top: 20px;
  left: 20px;
  background-color: rgba(0,0,0,0.6);
}

.CSS-text a {
  color: white;
  -o-transition: all 1s ease-in-out;
  -webkit-transition: all 1s ease-in-out;
  -moz-transition: all 1s ease-in-out;
  transition: all 1s ease-in-out;
  -moz-text-decoration-line: none;
  text-decoration-line: none;
  text-transform: uppercase;
  font-size: 32px;
  padding: 20px;
  display: block;
  cursor: pointer;
}

.CSS-text a:hover {
  background-color: rgba(20,20,20,0.4);
  color: #c8c8c8;
}

.CSS-text a:link {
  color: white;
  -o-transition: all 1s ease-in-out;
  -webkit-transition: all 1s ease-in-out;
  -moz-transition: all 1s ease-in-out;
  transition: all 1s ease-in-out;
  -moz-text-decoration-line: none;
  text-decoration-line: none;
  text-transform: uppercase;
  font-size: 32px;
  padding: 20px;
  display: block;
  cursor: pointer;
}

.CSS-text a:link:hover {
  background-color: rgba(20,20,20,0.4);
  color: #c8c8c8;
}

Now we need to apply this style:


<.div( ^.classSet( p.className.getOrElse("") -> true
      ),
      CSS.slide,
// Other stuff
)

 

 

Animated Loader

Now I have finally made css for the Slider itself. But I want to write css for the first slide, that is an animated loader:

The loader consists of 2 div elements. These elements have many similar properties, so we can use mixin again.


val loader = mixin(
    position.absolute,
    borderRadius(200.px),
    border := "6px solid #fff",
    borderTopColor(c"#fff"),
    borderLeftColor(c"#555"),
    borderRightColor(c"#555"),
    borderBottomColor(c"#fff"),
    top(50 %%),
    left(50 %%),
    animationIterationCount.infinite,
    animationName(rotate),
    animationTimingFunction.linear
  )

  val up = style(
    loader,
    height(200.px),
    width(200.px),
    animationDuration := "3s",
    marginTop(-100.px),
    marginLeft(-100.px)
  )

  val down = style(
    loader,
    height(150.px),
    width(150.px),
    animationDuration := "1s",
    marginTop(-75.px),
    marginLeft(-75.px)
)

 

Now we should define our animation:


val rotate0 = keyframe(
    transform := "rotate(0deg)"
  )
  val rotate180 = keyframe(
    transform := "rotate(180deg)"
  )
  val rotate360 = keyframe(
    transform := "rotate(360deg)"
  )

  val rotate = keyframes(
    (0%%) -> rotate0,
    (50%%) -> rotate180,
    (100%%) -> rotate360
)

 

Very pleasant and concise syntax. Here is generated code:


@keyframes CSS-rotate {
  0% {
    -moz-transform: rotate(0deg);
    -webkit-transform: rotate(0deg);
    -o-transform: rotate(0deg);
    transform: rotate(0deg);
  }

  50% {
    -moz-transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    transform: rotate(180deg);
  }

  100% {
    -moz-transform: rotate(360deg);
    -webkit-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }

}

 

Stop, it’s not enough for me! I need @-webkit-keyframes CSS-rotate. I hope that the authors will fix this issue.

Now we need to define generated classes of our elements:


SlideProps(style = Some(js.Dictionary(
        "backgroundColor" -> "gray"
      )), containers = Seq(
        SlideContainer(elementType = "block", className = CSS.up.htmlClass),
        SlideContainer(elementType = "block", className = CSS.down.htmlClass))
)

 

Now we have the whole css for our Slider and we wrote it in Scala. Hooray!

 

Why?

That’s a good question.

It took me three evenings to write styles using ScalaCSS. Of course, I was learning. But there was a problem after every css property! The syntax must be intuitive, but it is not.

The unclear syntax may affect the popularity. But it’s not the worst problem. Look at these numbers:

 

Size of compiled optimized javascript file:

Without ScalaCSS – 187 Kb
With ScalaCSS:

Only body element (5 lines of code) – 304kB

Only Slider – 397Kb

The whole example – 411Kb

 

As we can see, usage of only one style gives us increase of almost 120kB. And we get additional kilobytes in file size for every line of code!

At the same time, the generated css contains only 225 lines of code, and size of uncompressed file is 6Kb.

Don’t forget: size matters in web-applications!

 

Another thing is compile time. It took 97 seconds to make fast optimization. And it took 97 second to make full optimization after that. Are you kidding! May be my notebook is not the fastest in the world, but It takes only one second while using LESS.

 

All this is a consequence of the fact, that the authors were trying to create Scala code that must be compiled to javascript code and then javascript code  while running must produce CSS code.

I thinks it’s a wrong way. Have you ever seen a javascript library, that does it. When javascript is running, it should take css as a string!

More, why one should bother with type safety while working with css? What is the purpose of all these warnings about conflicts. There are no conflicts in CSS. CSS is very safe language. When you have an error in one property, it will not crash the whole style. And CSS has rules to resolve conflicts.

 

As a conclusion

ScalaCSS has many interesting ideas. Its authors have made great work. But, IMHO, the library has to main issues:

  1. File size and compilation time.
  2. Syntax, which should be much better to get the developer to abandon css.

 

See the full code of this example

 

 

Please follow and like us:

About Alexandre Kremlianski

Scala / Scala.js / JavaScript programmer

Leave a Reply

Your email address will not be published. Required fields are marked *