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:
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:
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 | |
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 de500x470px
.
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 border-width
.
Propiedad | Valor |
---|---|
border-image-width | 1 | |
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
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 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 deborder-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:
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;
}