Can I somehow use CSS custom properties (or something else) to combine pieces of transform?

530 views Asked by At

Foreword

Currently, when I want an element on the page to be customizable in different respects, I define a CSS module for it, and in that module I use custom properties to define CSS properties; those custom properties are defined by other modules or maybe simply in the * { } ruleset, to make them widely available.

In the silly example below, for instance, the .box class is for drawing a box around the text; however, two of the properties it sets are not really hardcoded to two specific values, but to two custom properties which one should set by other means, for instance via the other three classes in the snippet.

/* global rulesets to set some "constants" */
.colored {
  --theme-color: green;
}

.shapes-hard {
  --border-radius: 0;
}

.shapes-soft {
  --border-radius: 5px;
}

/* module that uses existing custom properties */
.box {
  text-align: center;
  background-color: var(--theme-color, grey);
  border-radius: var(--border-radius, 0);
}
<style> div { width: 10em; } </style>
<div class="shapes-hard box">
foo
</div>
<br>
<div class="colored shapes-soft box">
bar
</div>

I'm generally happy with the flexibility that this approach gives.

Actual question

However, what I haven't quite understood is how I can extend this approach to those CSS properties that accept more than 1 value, e.g. transform.

As an example (and it's actually my use case), take this spinning loader and imagine that I want to use it as in the example, but that I also want to be able to put it in the middle of another element, via manual positioning (see this comment).

.container {
  position: relative;
  width: 5em;
  height: 5em;
}

.container::after {
  content: "";
  display: block;
  width: 5em;
  height: 5em;
  border: 1px solid black;
}

.loader {
  border: 10px solid #f3f3f3;
  border-radius: 50%;
  border-top: 10px solid #3498db;
  width: 2em;
  height: 2em;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: translate(-50%, -50%) rotate(0deg); }
  100% { transform: translate(-50%, -50%) rotate(360deg); }
}

.center-origin {
  position: absolute;
  left: 50%;
  top: 50%;
}
<div class="container">
<div class="loader center-origin"></div>
</div>

As you can see, the module .center-origin does essentially just one thing, that is translating the element's origin in the exact center of the parent (well, the closest postitioned parent).

What I don't like is that the translate(-50%, -50%) part is hardcoded in a module it doesn't belong to. For instance, I can't reuse the spin animation for things that don't require translate(-50%, -50%).

I would rather have a module like this

.center-self /* maybe not appropriate name */ {
  transform: translate(-50%, -50%);
}

and a corresponding markup like this

<div class="container">
<div class="loader center-origin center-self"></div>
</div>

but then I wouldn't know how I can stitch together the part of transform set by .center-self and the part set by @keyframes spin.


Here are some related questions, which seems to answer "it's not possible", but they are quite old, so maybe something's changed in the meanwhile.

1

There are 1 answers

0
A Haworth On

Although there may be alternative ways of doing specifically what you want (e.g. instead of transform using margin as @temaniafif has suggested), your question is an example of a general problem of 'remembering' the various components which go to make up a current transform.

One way is to use CSS variables. In your case setting variables for the x and y translations to be 0 and then just specifically setting them 'in the right place' which in your code is in the class that says the element is to be centered, not in the keyframes.

:root {
  --tx: 0;
  --ty: 0;
}

.container {
  position: relative;
  width: 5em;
  height: 5em;
}

.container::after {
  content: "";
  display: block;
  width: 5em;
  height: 5em;
  border: 1px solid black;
}

.loader {
  border: 10px solid #f3f3f3;
  border-radius: 50%;
  border-top: 10px solid #3498db;
  width: 2em;
  height: 2em;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% {
    transform: translate(var(--tx), var(--ty)) rotate(0deg);
  }
  100% {
    transform: translate(var(--tx), var(--ty)) rotate(360deg);
  }
}

.center-origin {
  position: absolute;
  left: 50%;
  top: 50%;
  --tx: -50%;
  --ty: -50%;
}
<div class="container">
  <div class="loader center-origin"></div>
</div>