Opacidade em background-image sem afetar textos
Tags: tutorial, braziliandevs, html, css
Published: 2023-03-01 17:23:20

Quando a gente trabalha com desenvolvimento front-end, inevitavelmente a gente vai precisar trabalhar opacidade em alguma imagem ou elemento, seja por questões de design, seja por acessibilidade visual. A aplicação de opacidades em imagens é notoriamente comum em _banners_, por exemplo, que são um recurso visual muito legal pra deixar sua página mais bonita e mais chamativa.

E aí você cria a sua `<section class="banner">` todo feliz, insere seu texto, vai no seu <abbr title="Cascading Style Sheets">CSS</abbr> e coloca uma `background-image` bem bonita, um `opacity: 0.5` e... o que acontece?

![Banner totalmente semi-transparente](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15op2h6orncv5df3gn09.JPG)

Isso mesmo. Você acabou de deixar o seu banner todo semi-transparente com a opacidade aplicada. 🥲

E como resolver isso? Vem comigo que eu te mostro!

![Gif do personagem Morpheus do filme Matrix](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9dueoifokf2uyaeinl4d.gif)

> _**Sumário**_
> _[Porque a opacidade afetou tudo?](#porque-a-opacidade-afetou-tudo)_
> _[Criando um pseudo-elemento para o background](#criando-um-pseudoelemento-para-o-background)_
> _[Estilizando o ::before](#estilizando-o-before)_
> _[Uma palavra sobre acessibilidade](#uma-palavra-sobre-acessibilidade)_
> _[Forma alternativa com a tag <img>](#forma-alternativa-com-a-tag-img)_

---

### Porque a opacidade afetou tudo?

No exemplo inicial, apesar de termos colocado a imagem de fundo no `background`, ela ainda está renderizada no mesmo elemento `.banner` junto com o seu texto. Dessa forma, a opacidade aplicada na `section` será aplicada a todos os elementos, mesmo os que estiverem só no `background`.

```html
<div class="banner">
    <h1>Meu banner bonitão</h1>
</div>
```
```css
.banner {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50vh;
  width: 100%;
  background: url("preview.jpg") bottom no-repeat;
  background-size: cover;
  opacity: 0.5;
}
```
Nesse caso, para que a opacidade seja aplicada unicamente na sua imagem, esta deveria estar em um elemento diferente do que contém o seu texto. Dessa forma, você consegue aplicar opacidade em um sem afetar o outro. E pra fazer isso, vamos precisar criar um `pseudo-elemento` para exibir nossa imagem de fundo.

> _Caso queira saber mais sobre as diferenças entre pseudo-elementos e pseudo-classes, você pode [ler este outro artigo que escrevi!](https://sucodelarangela.medium.com/entendendo-css-pseudo-classes-e-pseudo-elementos-cbebcfdb1b4d)_

---

### Criando um pseudo-elemento para o background

Para esse caso específico, podemos usar tanto o pseudo-elemento `::before` quanto o `::after`. O que cada um deles faz é <mark>criar um "elemento falso" no seu <abbr title="Hyper Text Markup Language">HTML</abbr> dentro do seletor indicado, mas **antes** ou **depois** do conteúdo existente no HTML.</mark> Nossa `section` com `::before` e `::after` ficaria assim se você inspecionar o código no DevTools:

```html
<div class="banner">
    ::before
    <h1>Meu banner bonitão</h1>
    ::after
</div>
```

Vamos utilizar no nosso exemplo apenas `::before`, não mexeremos no HTML. No nosso CSS, precisamos:

**a)** definir um tamanho para a nossa `section` através das propriedades `width` e `height`: a largura normalmente fica em 100% (para que o _banner_ ocupe toda a largura da página) e a altura fica à seu critério;

**b)** posicionar o texto onde desejarmos utilizando um `display` adequado e suas propriedades de posicionamento. No nosso exemplo, estou centralizando o texto com `display: flex` e suas propriedades específicas:

```css
.banner {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50vh;
  width: 100%;
}
```

---

### Estilizando o ::before

Agora vamos estilizar o nosso pseudo-elemento `::before`. Para isso, vamos:

**a)** Usar o seletor apropriado `.banner::before`;

**b)** Adicionar a propriedade `content`, **obrigatória** para uso de `::before` ou `::after`. Como não teremos nenhum conteúdo além da imagem de fundo, essa propriedade vai como uma _string_ vazia;

**c)** Para manipular nosso pseudo-elemento, precisamos definir ele com `position: absolute`. Isso vai nos permitir deixar ele do mesmo tamanho do elemento `.banner` e deixar o texto sobreposto;

**d)** Adicionar nosso `background-image` com a url da imagem e definir o tamanho da imagem com `inset: 0`. Essa propriedade equivale à `top: 0; right: 0; bottom: 0; left: 0`.

Até agora, nosso resultado está ficando assim:

<figure>
![Resultado parcial da imagem na tela](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5e8rkvt3s2jfux9ow76.JPG)
<figcaption>Parece que vai dar tudo errado, mas confia!</figcaption>
</figure>
>

Nossa imagem está pegando a tela inteira, mas porque?

Quando usamos `position: absolute` o nosso elemento "sai" do fluxo da página, ou seja, é como se ele ficasse "flutuando" acima de todos os outros elementos e ele toma conta de toda a página porque definimos o `inset: 0`. Para corrigir isso, precisamos dar uma referência para esse elemento absoluto, dizendo pra ele se posicionar **em relação à outro elemento na página**. Esse elemento que será nossa referência é o nosso `.banner`, que deverá ter `position: relative`:

```css
.banner {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50vh;
  width: 100%;
  position: relative; /* agora o ::before usa o .banner de referência, e não a página toda */
}
```

Só em adicionar o `position: relative` no `.banner`, já melhoramos nosso resultado:

![Resultado parcial com posição relativa](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjfg1z8e9n7ae2z5zs4b.JPG)

Agora vamos apenas finalizar os ajustes da imagem e colocar ela por trás do texto:

**e)** usamos `background-size: cover` para fazer com que a imagem ocupe toda a área da `session.banner` sem distorcer e sem repetir;

**f)** posicionamos a imagem com `background-position`. Nesse exemplo, posicionar pelo `bottom` ficou mais legal;

**g)** colocamos um `z-index: -1` pra forçar a imagem a ir "pra baixo" do texto (nem sempre isso é necessário, mas é sempre bom garantir que a imagem fique sempre abaixo); e

**h)** finalmente, aplicamos a opacidade desejada na imagem! Agora a imagem ficará semi-transparente sem afetar o texto.

Nosso código final ficou assim:

```html
<div class="banner">
    <h1>Meu banner bonitão</h1>
</div>
```

```css
.banner {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50vh;
  width: 100%;
  position: relative;
}

.banner::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("preview.jpg");
  background-size: cover;
  background-position: bottom;
  z-index: -1;
  opacity: 0.5;
}
```

E aqui você pode ver o código em ação:

{% codepen https://codepen.io/sucodelarangela/pen/mdxjZRG %}

---

### Forma alternativa com a tag img

Uma forma bem mais fácil de ter o mesmo resultado acima seria adicionar a imagem de fundo usando uma tag `<img>` diretamente no seu HTML e fazendo alguns ajustes no CSS (ainda assim, usando `position` para fazer a sobreposição de imagem e texto). O código seria mais ou menos assim:

```html
<div class="banner">
  <img src="preview.jpg" alt="Pessoa contemplando uma paisagem psicodélica em um lugar remoto">
  <h1>Meu banner bonitão</h1>
</div>
```
```css
/* Nosso banner continua igual */
.banner {
  position: relative;
  height: 50vh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Estilizando a tag img */
img {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: bottom;
  z-index: -1;
  opacity: 0.5;
}
```

Vocês podem perceber que existem algumas coisas diferentes do nosso exemplo anterior:

- Como nossa imagem não está no background, trocamos `background-size` e `background-position` por `object-fit` e `object-position`, respectivamente, para manter a correta proporção e alinhamento da imagem;

- Definimos o tamanho da nossa imagem com `width` e `height` em 100% para que ela fique do tamanho da `session.banner`, pois o `inset: 0` só vai fazer a imagem ocupar uma parte da `session` por não estar no background.

> _Então se assim foi mais fácil, porque não fazer sempre assim?_

Por uma simples questão: <mark>Nem sempre uma `img` direto no HTML atende às melhores práticas de acessibilidade!</mark>

Na maioria das vezes, imagens de _banners_ são meramente ilustrativas, **não contendo significado semântico** para a página. Se esse é o seu caso, opte por usar as imagens por meio do CSS, através do `background-image`. Dessa forma, os leitores de tela não farão a leitura da imagem para o usuário que necessita desse recurso (vale ressaltar que a acessibilidade da página deve deixar a leitura por parte desses dispositivos o mais clara possível).

Porém, se você está colocando uma imagem que tem importância em ser descrita para o usuário, agregando valor semântico ao conteúdo da sua página, então a melhor opção é usar a tag `<img>`, não esquecendo de usar o atributo `alt` para dar uma descrição completa e semântica do que a imagem representa. E nada de escrever "imagem de coisa tal" ou "foto de uma coisa": os leitores de tela já leem pro usuário que ele está vendo uma imagem, então escrever "imagem", "foto" ou "ilustração" no `alt` só vai deixar o seu conteúdo redundante!

---

É importante que nós, enquanto desenvolvedores front-end, tenhamos cada vez mais conhecimento nas boas práticas de escrita de código, acessibilidade e também SEO. Espero que este artigo traga um pouco mais de conhecimento e dê aquela forcinha nos seus projetos futuros! E se Acessibilidade é um assunto que chamou sua atenção, dá uma olhada no conteúdo abaixo da [**Alura**](https://www.alura.com.br/) #paz

{% youtube https://www.youtube.com/watch?v=gnO_6NDfQHI&ab_channel=Alura %}