La función ray()

Definir porciones de un círculo


En el artículo anterior aprendimos a utilizar las propiedades offset para definir trayectos animables. Ahora vamos a aprender a utilizar la función ray(), que es una de las formas para crear trayectos utilizables con la propiedad offset-path.

¿Qué hace la función ray()?

La función ray() nos permite definir un trayecto como una porción de un círculo, es decir, recorriendo una ruta del radio de un círculo, estableciendo un ángulo como hacemos al dibujar con CSS un gradiente cónico.

Observa la siguiente imagen, donde he dibujado un trazo discontinuo para representar un círculo (rojo) y los trayectos de esas porciones (negro) del círculo. En este caso tenemos 3 elementos .box dentro de un .container con un ángulo diferente cada uno:

La propiedad offset-path con la función ray()

En este ejemplo cada una de las cajas utiliza el siguiente ángulo:

  • La primera caja (violeta) tiene un ray() con ángulo de 90deg (equivale a 0.25turn).
  • La segunda caja (rojo) tiene un ray() con ángulo de 180deg (equivale a 0.5turn).
  • La tercera caja (rosa) tiene un ray() con ángulo de 245deg (equivale a 0.68turn).

En este caso, sólo estamos utilizando el ángulo en la función ray(), su sintaxis más simple. También se puede definir el tamaño con una de las palabras clave closest-side, closest-corner, farthest-side, farthest-corner o sides. Si no se indica ninguna, se asume el valor closest-side.

La función ray() tiene más parámetros, pero veamos el ejemplo anterior de la imagen en código, junto a lo que hemos aprendido hasta ahora:

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  background: #aaa;
  min-height: 400px;
}

.box {
  width: 50px;
  height: 50px;
  animation: move 2s alternate infinite;
}

@keyframes move {
  0% { offset-distance: 0%; }
  100% { offset-distance: 100%; }
}

.box-1 {
  background: indigo;
  offset-path: ray(90deg);
}
.box-2 {
  background: crimson;
  offset-path: ray(180deg);
}
.box-3 {
  background: deeppink;
  offset-path: ray(245deg);
}
<div class="container">
  <div class="box box-1"></div>
  <div class="box box-2"></div>
  <div class="box box-3"></div>
</div>

La función ray()

Ahora que tenemos una idea de la función ray(), veamos su sintaxis desde la más simple hasta la más compleja. Podemos añadir sólo su ángulo, como añadir también su tamaño, posición e incluso si queremos mantenerla dentro del contenedor:

ValoresDescripción
ray()Indica un segmento en dirección .
ray( )Idem, pero con un tamaño predefinido .
ray( at )Idem, pero colocando el elemento en una posición concreta.
ray( at contain)Idem, pero manteniendo el elemento dentro del contenedor.

Pero todo esto es mucho más fácil verlo con un ejemplo. Observa la siguiente demo interactiva, donde puedes modificar en vivo el ángulo, el tamaño y la opción contain. Aunque no se muestra en la demo, también se podría añadir la posición tras la palabra clave at para colocar de forma estática el elemento en una posición concreta:

.container {
  width: 400px;
  height: 400px;
  display: grid;
  place-items: center;
  margin: auto;
  border-radius: 50%;
  background: #aaa;

  & .box {
    width: 50px;
    height: 50px;
    background: indigo;
    offset-path: ray(0.25turn);
    animation: move 2s alternate infinite;
  }
}

@keyframes move {
  0% { offset-distance: 0%; }
  100% { offset-distance: 100%; }
}

label {
  display: block;
  font: monospace;
}

code {
  position: absolute;
  top: 1rem;
  right: 1rem;
}
const size = document.querySelector("select.size");
const output = document.querySelector("output");
const input = document.querySelector("input");
const box = document.querySelector(".box");
const code = document.querySelector("code");
const contain = document.querySelector(".contain");

const update = () => {
  const sizeValue = size.options[size.selectedIndex].value;
  const containValue = contain.checked ? " contain" : "";
  output.textContent = input.value + "turn";
  const rayValue = `ray(${output.textContent} ${sizeValue}${containValue})`;
  box.style.setProperty("offset-path", rayValue);
  code.textContent = `offset-path: ${rayValue}`;
}

input.addEventListener("input", () => update());
size.addEventListener("input", () => update());
contain.addEventListener("input", () => update());
<label>
  Angle: <input type="range" min="0" max="1" step="0.05" value="0.25">
  <output>0.25turn</output>
</label>
<label>
  Size: <select class="size">
    <option value="closest-side">closest-side</option>
    <option value="farthest-side">farthest-side</option>
    <option value="closest-corner">closest-corner</option>
    <option value="farthest-corner">farthest-corner</option>
    <option value="sides">sides</option>
  </select>
</label>
<label>
  Contain: <input class="contain" type="checkbox">
</label>
<code>offset-path: ray(0.25turn)</code>

<div class="container">
  <div class="box"></div>
</div>

Si no sabes como funcionan los parámetros de mencionados anteriormente, echa un vistazo a formas y tamaños del gradiente radial, ya que lo explicamos ahí más detalladamente.

La propiedad offset-position

La propiedad offset-position nos permite establecer la posición del trayecto. Esto puede ser útil en casos donde nos interesa posicionar el elemento dependiendo del trayecto que se utilice.

ValorDescripción
autoEl valor auto es el valor por defecto.
Indicamos la distancia a mover la posición inicial del trayecto.

Veamos un ejemplo interactivo en acción. Observa que será una posición, indicando el Eje X y el Eje Y. En nuestro caso haremos una animación que permita mover un elemento desde la posición indicada, hasta la posición 100% 100%, es decir, esquina inferior-derecha:

body {
  min-height: 275px;
}

.container {
  width: 500px;
  height: 200px;
  margin: auto;
  background: grey;
}

.element {
  width: 50px;
  height: 50px;
  background: indigo;
  offset-path: ray(25deg);
  offset-position: 0% 0%;
  animation: move 2s alternate infinite linear;
}

@keyframes move {
  to { offset-position: 100% 100%; }
}
const position = document.querySelector("select");
const element = document.querySelector(".element");

position.addEventListener("input", () => {
  const offsetPosition = position[position.selectedIndex].textContent;
  element.style.setProperty("offset-position", offsetPosition);
});
<label>
  <code>offset-position (inicial): </code>
  <select>
    <option checked>0% 0%</option>
    <option>50% 0%</option>
    <option>100% 0%</option>
    <option>50% 50%</option>
    <option>0% 100%</option>
    <option>50% 100%</option>
  </select>
</label><br>

<code>offset-position (final): 100% 100%</code>

<div class="container">
  <div class="element"></div>
</div>

Observa que en la animación sólo hemos establecido el to, es decir, la posición final. Esto hará que se mueva hacia esa posición, partiendo del valor que tenía offset-position en el elemento previamente.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev