Bordes con imágenes

La técnica 9-slice para crear bordes con imágenes


Una de las limitaciones que tenía CSS, es que, si en lugar de utilizar los bordes de los que disponemos en CSS (sólidos, punteados, etc...) quisieramos hacer algo un poco más complejo con imágenes, podría volverse una tarea muy complicada. Por esa razón, CSS3 incorporó en su momento un sistema para crear bordes extensibles basados en una imagen de molde.

La técnica 9-slice

Dicho sistema se denomina 9-slice (muy utilizado en videojuegos) y se basa en delimitar una imagen trazando cuatro líneas (en rojo). Esto hará que la imagen quede dividida en 9 fragmentos, donde el fragmento central es descartado y el resto es utilizado de molde para los bordes de un elemento:

Sistema 9-slice

De esta forma, los fragmentos 1, 3, 7 y 9 se utilizarán para las esquinas y los fragmentos 2, 4, 6 y 8 se utilizarán para los bordes laterales, pudiendo expandirlos si se requiere y considera necesario con alguna de las propiedades que veremos a continuación.

Propiedades de border-image

Las propiedades que podemos utilizar para crear bordes con imágenes son las siguientes:

Propiedad Descripción
border-image-source Imagen a utilizar para los bordes con imágenes mediante la técnica 9-slice.
border-image-width Especifica el grosor de la imagen utilizada para el borde.
border-image-slice Distancia desde el extremo donde se hace el punto de corte en la imagen.
border-image-outset Tamaño en el que el borde crece hacia fuera. Una especie de padding del borde.

Estas propiedades pueden ser algo complicadas, por lo que vamos a repasar cada una de ellas haciendo varios ejemplos progresivos.

Explicación del objetivo

Utilizaremos la siguiente imagen expandible (izquierda), que simula ser un antiguo carrete fotográfico. Las líneas rojas no forman parte de la imagen original, sino que se utilizan en este ejemplo para dejar claro cuáles serían los límites marcados con la propiedad border-image-slice, que veremos más adelante. Una vez hecho esto, conseguiremos el resultado de la imagen de la derecha, en el cuál podremos ampliar el texto del elemento como queramos, ya que se adaptará a su contenido:

Ejemplo con border-image en CSS

Comencemos con el primer ejemplo. Observa el siguiente código CSS, donde hacemos varias cosas iniciales sin llegar a utilizar border-image aún:

  • 1️⃣ Tenemos un elemento en el HTML, con contenido en su interior.
  • 2️⃣ Establecemos un ancho con width para nuestro elemento.
  • 3️⃣ Indicamos un border básico, usando una variable de CSS para reutilizarla más adelante.
<div>
  <p>Contenido con un <code>border</code> básico:</p>
  <div class="element">
    Este elemento debería ser más grande ya que
    tiene más contenido que mostrar.
  </div>
</div>
.element {
  --border-size: 42px;

  width: 250px;
  border: var(--border-size) solid black;
}

Como comprobarás, hemos establecido un borde básico, de 42px de grosor, de color negro, que bordea el contenido. Nuestra intención es comenzar a utilizar border-image para utilizar una imagen, en lugar de un color sólido.

La propiedad border-image-source

La primera propiedad que convendría analizar es border-image-source. En ella, mediante la función CSS url(), indicaremos una imagen para crear nuestro borde con imágenes. Funciona igual que cuando usamos url() en la propiedad background-image:

Propiedad Valor
border-image-source none | | url("imagen.png")

Recuerda que en CSS, si podemos utilizar imágenes, también podemos utilizar gradientes. Pero eso lo veremos más adelante. Veamos un ejemplo utilizando la propiedad border-image-source:

.element {
  --border-size: 42px;

  width: 250px;
  border: var(--border-size) solid black;
  border-image-source: url("photo-9slice.png");
}
<div>
  <p>Imagen con <code>border-image-slice</code>:</p>
  <div class="element">
    Este elemento debería ser más grande ya que
    tiene más contenido que mostrar.
  </div>
</div>

Comprobarás que el resultado es, cuanto menos, extraño. Al establecer una imagen con border-image-source, por defecto, lo que hace es establecer la imagen de modo que aparezca en cada esquina, redimensionándola. Esto dista mucho de lo que queremos conseguir, pero es un primer paso. Ahora utilizaremos otras propiedades para ajustarlo y dejarlo a nuestro gusto.

Es importante tener presente el tamaño de la imagen que utilicemos. En nuestro caso, la imagen photo-9slice.png tiene un tamaño de 500x470px.

La propiedad border-image-width

La propiedad border-image-width indica el tamaño que tendrá el borde de la imagen. El tamaño puede ser indicado con unidades (píxeles o porcentajes, por ejemplo), como estamos acostumbrados. Sin embargo, también puedse usar un (sin ninguna unidad), esto lo tomará como un múltiplo del tamaño establecido en border-width.

Propiedad Valor
border-image-width 1 | | | auto

En nuestro ejemplo hemos indicado un 42px, que coincide con el border-width, pero he hecho eso por simplicidad, porque es más intuitivo. Sin embargo, si utilizamos 1 en border-image-width significa que tomará el mismo valor de border-width. Un 2 sería el doble, etc... Si no le indicamos ningún valor, toma 1 por defecto.

.element {
  --border-size: 42px;

  width: 250px;
  border: var(--border-size) solid black;
  border-image-width: var(--border-size);
}
<div>
  <p>Imagen con <code>border-image-width</code>:</p>
  <p>Propiedad <code>border-width</code>:
    <input class="bw" type="range" min="0" max="500" value="42">
    <output>42px</output>
  </p>
  <p><input class="show-image" type="checkbox"> Mostrar imagen</p>
  <p class="biw-group" hidden>Propiedad <code>border-image-width</code>:
    <input class="biw" type="range" min="0" max="500" value="42">
    <output>42px</output>
  </p>
  <div class="element">
    Este elemento debería ser más grande ya que
    tiene más contenido que mostrar.
  </div>
</div>
const element = document.querySelector(".element");
const [biwInput, biwOutput] = document.querySelectorAll("input.biw, input.biw + output");
const [bwInput, bwOutput] = document.querySelectorAll("input.bw, input.bw + output");
const showImage = document.querySelector("input.show-image");
const biwGroup = document.querySelector("p.biw-group");

biwInput.addEventListener("input", () => {
  biwOutput.value = element.style.borderImageWidth = `${biwInput.value}px`;
});

bwInput.addEventListener("input", () => {
  bwOutput.value = element.style.borderWidth = `${bwInput.value}px`;
});

showImage.addEventListener("input", () => {
  biwGroup.toggleAttribute("hidden");
  element.style.borderImageSource = showImage.checked ? `url("photo-9slice.png")` : `none`;
});

Juega un poco con los valores del tamaño width del elemento (sin imagen) y observa el espacio que ocupa. Luego, prueba a activar el border-image-source y cambiar su tamaño con border-image-width para comprender como funciona. No le verás mucho sentido de momento, ya que nos falta aprender algunas propiedades más, pero sabrás como funciona y podrás alterar el resultado final.

Por otro lado, recuerda que de la misma forma que propiedades como margin o padding, en la propiedad border-image-width se puede indicar 1, 2, 3 o 4 parámetros , haciendo referencia a la parte superior, derecha, abajo e izquierda.

La propiedad border-image-slice

La propiedad border-image-slice define el desplazamiento de las líneas divisorias de la imagen, o lo que es lo mismo, el tamaño de los bordes de la imagen original que se escalará al tamaño de los bordes indicado en la propiedad anterior.

En este caso, solo podemos utilizar valores o . Por defecto, el valor es de 100% (tamaño de ancho completo de la imagen, como vimos antes), pero si usamos números sin unidad, simbolizarán los píxeles de recorte. También se puede especificar 1, 2, 3 ó 4 parámetros , como en margin o padding.

Propiedad Valor
border-image-slice 100% | | fill

Por otro lado, añadiendo la palabra clave opcional fill, indicaremos que queremos rellenar el elemento con el fondo del fragmento 5, que por defecto es descartado. Útil en casos que quieras aprovechar el fondo.

En nuestro caso, nos podría valer un valor aproximado a 111 (111 píxeles de recorte) o 23%, ya que es más o menos la cantidad apropiada para establecer el límite deseado tanto de ancho como de alto.

body {
  height: 350px;
}

.element {
  --border-size: 42px;

  width: 400px;
  height: 200px;
  border: var(--border-size) solid black;
  border-image-width: var(--border-size);
  border-image-source: url("photo-9slice.png");
  border-image-slice: 500;
  display: grid;
  place-items: center;
}

select {
  padding: 1rem;
  font-size: 1.5rem;
}
<p>
  Propiedad <code>border-image-slice</code>:
  <input type="range" min="0" max="500" value="500">
  <output>500</output>
</p>

<div class="element">
  Aquí el contenido.
</div>
const element = document.querySelector(".element");
const input = document.querySelector("input");
const output = document.querySelector("output");

input.addEventListener("input", () => {
  output.value = element.style.borderImageSlice = input.value;
});

En este caso no se aprecia porque la imagen tiene un interior vacío (en blanco), sin embargo, si tuviera contenido y quisieramos utilizarlo, no tendríamos más que añadir fill al final de los valores de border-image-slice. Lo veremos más adelante.

La propiedad border-image-outset

La propiedad border-image-outset establece la cantidad en la que se extiende el borde más allá de su límite. Muy útil para compensar la imagen si se extiende hasta el contenido. Usar con cuidado, ya que puede desplazar el contenido.

Propiedad Valor
border-image-outset 0 |

Por defecto, esta propiedad no tiene desplazamiento, o lo que es lo mismo, el valor por defecto de border-image-outset es de 0.

body {
  height: 450px;
}

.element {
  --border-size: 42px;

  width: 200px;
  height: 125px;

  margin: 100px;
  border: var(--border-size) solid black;
  /* border-image */
  border-image-width: var(--border-size);
  border-image-source: url("photo-9slice.png");
  border-image-slice: 111;
  border-image-outset: 0px;
  display: grid;
  place-items: center;
}

select {
  padding: 1rem;
  font-size: 1.5rem;
}
<p>
  Propiedad <code>border-image-outset</code>:
  <input type="range" min="0" max="100" value="0">
  <output>0px</output>
</p>

<div class="element">
  Aquí el contenido.
</div>
const element = document.querySelector(".element");
const input = document.querySelector("input");
const output = document.querySelector("output");

input.addEventListener("input", () => {
  output.value = element.style.borderImageOutset = input.value + "px";
});

Con border-image-outset podemos controlar esa extensión, sin embargo, sería genial que también pudieramos hacer algo con ese estiramiento tan feo. Para ello, usaremos la siguiente propiedad.

La propiedad border-image-repeat

En algunas ocasiones, el modo en que se repite la imagen de borde no es la apropiada, o al menos, no es la que más se adapta a nuestro caso específico. Este comportamiento se puede modificar mediante la propiedad border-image-repeat y es de los detalles más potentes e interesantes de border-image:

Propiedad Valor Significado
border-image-repeat [repetición en X e Y] 1 parámetro. Comportamiento de repetición en ambos ejes.
[rep. en X] [rep. en Y] 2 parámetros. Comportamiento de repetición por separado.

Con dicha propiedad se establece como deben comportarse los fragmentos del borde y el tipo de repetición que deben efectuar. Se puede usar la modalidad de un parámetro en la que se aplica a todos los bordes, o la modalidad de dos parámetros donde estableces diferente comportamiento para los bordes horizontales y verticales.

Esta propiedad puede tomar los siguientes valores:

Valor Significado
stretch Los bordes se estiran hasta rellenar el área. Es el valor por defecto.
repeat Los bordes se repiten hasta rellenar el área.
round Igual que repeat, pero estira los fragmentos individualmente hasta rellenar el área completa.
space Igual que repeat, pero añade espacios hasta rellenar el área completa.

Para verlo más claramente, echemos un vistazo a esta representación visual del comportamiento de cada uno:

Border-image-repeat values

Es importante recalcar los dos últimos valores (round y space) actúan igual que repeat, pero con un comportamiento ligeramente diferente que nos puede interesar en el caso de que la zona repetida quede descompensada.

Utilizando la imagen y código CSS anterior, obtendríamos un resultado similar a este, que se adaptaría sólo al contenido que escribamos dentro del elemento HTML con clase borde :

.element {
  width: 600px;
  padding: 10px;

  border: 42px solid black;
  border-image-width: 1;  /* Mismo tamaño que border-width: 42px */
  border-image-source: url("photo-9slice.png");
  border-image-slice: 111;
  border-image-repeat: round;
}

select {
  padding: 0.5rem 1rem;
  font-size: 1.5rem;
  margin: 1rem 0;
}
<p>Propiedad <code>border-image-repeat</code>:</p>
<select>
  <option>stretch</option>
  <option>repeat</option>
  <option>round</option>
  <option>space</option>
</select>

<div class="element">
  En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho
  tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,
  rocín flaco y galgo corredor. Una olla de algo más vaca que carnero,
  salpicón las más noches, duelos y quebrantos los sábados, lantejas los
  viernes, algún palomino de añadidura los domingos, consumían las tres
  partes de su hacienda.
</div>
const select = document.querySelector("select");
const element = document.querySelector(".element");
select.addEventListener("change", () => {
  element.style.borderImageRepeat = select[select.selectedIndex].textContent;
});

También es posible indicar dos parámetros en la propiedad border-image-repeat, de modo que cada parámetro se aplica a cada eje (horizontal y vertical).

Atajo: Bordes con imágenes

Como suele ser costumbre, este tipo de propiedades tienen un atajo para ahorrar espacio y escribirlo todo de una sola vez. En este caso, la sintaxis es la siguiente:

div {
  /* border-image: <source> <slice> <width> <outset> <repeat> */

  border-image: url("photo-9slice.png") 23% 1 0 round;
}

¿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