# Fallbacks para a função CSS progress() em todos os navegadores

> Como reproduzir a função CSS progress() em navegadores sem suporte: calc() e clamp() para números, tan(atan2()) e @property para valores com unidade.

URL: https://dpw.dev/css/css-progress-fallbacks/  
Publicado em: 2026-10-19  
Categoria: css  
Tags: web-design-responsivo, tipografia, boas-praticas  
Autor: Tárcio Zemel

A **função CSS `progress()`** diz em que ponto um valor está entre dois limites, com um número de 0 a 1. Enquanto o suporte não chega a todos os navegadores, a mesma conta pode ser feita com `calc()`, `clamp()`, o truque do `tan(atan2())` e `@property`, e o fallback certo depende só das unidades envolvidas.

> Artigo baseado em [Fallbacks for CSS progress()](https://blog.master.dev/fallbacks-for-css-progress/).

A nova <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/progress" target="_blank" rel="noopener noreferrer">função CSS `progress()`</a> calcula em que ponto um valor está entre um limite inicial e um final, e devolve um número sem unidade entre `0` e `1`.

Enquanto o suporte dos navegadores não cresce, dá para calcular o progresso manualmente, usando a mesma mecânica que está por trás da função.

A matemática de `progress(value, start, end)` é simples:

```text
progresso = (valor atual - início) / (fim - início)
```

Isso também é conhecido como **normalização**.

![Reta numérica de 320px a 1200px com o valor 500px marcado. Acima da reta, os resultados: 0 no início, aproximadamente 0,2045 no valor e 1 no fim. Abaixo, o offset de 500 menos 320, igual a 180px, e o span de 1200 menos 320, igual a 880px. 180 dividido por 880 dá aproximadamente 0,2045.](/images/blog/css-progress-fallbacks/progress-normalizacao.webp)

*A conta do `progress()`: a distância do início até o valor (offset) dividida pela distância do início até o fim (span). Fora do intervalo, o resultado fica preso em 0 ou 1.*

Dependendo do tipo de valor com que você trabalha, implementar isso em CSS vai de um simples `calc()` até alguns truques de CSS mais complicados.

## Só números sem unidade

Se você trabalha apenas com números sem unidade (<a href="https://blog.master.dev/comparing-values-with-css-progress/" target="_blank" rel="noopener noreferrer">como no artigo de Preethi Sam</a>), dá para aplicar a fórmula direto no `calc()`. Para imitar o comportamento padrão de `progress()`, o exemplo usa `clamp()`.

Por exemplo, para descobrir onde `50` fica entre `10` e `100`:

```css
--progress: clamp(0, (50 - 10) / (100 - 10), 1);
```

Pronto, quase tão fácil quanto `progress()`.

## Trabalhando com unidades

Na maioria das vezes, a ideia é usar unidades como `px`, `rem` ou `vw` e, mesmo assim, obter um valor sem unidade.

Navegadores que suportam aritmética tipada (*typed arithmetic*) já fazem isso. As duas linhas a seguir devem produzir o mesmo resultado:

```css
--progress: clamp(0, (100vw - 20rem)/(100rem - 20rem), 1);
--progress: progress(100vw, 20rem, 100rem);
```

Infelizmente, isso ainda não funciona em todos os navegadores (olhando para você, Firefox).

É aí que entra o <a href="https://dev.to/janeori/css-type-casting-to-numeric-tanatan2-scalars-582j" target="_blank" rel="noopener noreferrer">truque do `tan(atan2())`</a>.

Daria para envolver cada valor nele, mas é possível otimizar mais.

A fórmula do progresso exige uma divisão, e `atan2(y, x)` já divide seus valores. Passe o **offset** (o deslocamento, `valor - início`) como primeiro argumento e o **span** (a extensão do intervalo, `fim - início`) como segundo.

Se todos os valores usam a mesma unidade, dá para colocá-los direto:

```css
--progress: tan(atan2(500px - 320px, 1200px - 320px));
```

![Triângulo retângulo com o cateto horizontal x igual ao span de 880px e o cateto vertical y igual ao offset de 180px. Três passos: 1, subtração, 500px menos 320px dá 180px e 1200px menos 320px dá 880px; 2, atan2(180px, 880px) dá um ângulo de aproximadamente 11.56deg; 3, tan(11.56deg) dá aproximadamente 0.2045, um número sem unidade.](/images/blog/css-progress-fallbacks/tan-atan2-remove-unidade.webp)

*`atan2()` recebe dois comprimentos e devolve o ângulo do triângulo formado por eles. `tan()` transforma esse ângulo de volta na razão `y ÷ x`, já sem unidade.*

## Misturando unidades

Normalmente, as unidades *não* vão bater. Em teoria, `atan2()` deveria lidar com unidades misturadas, e em alguns navegadores lida. Mas, para funcionar em todos, é preciso um pouco mais de trabalho.

Veja o caso da <a href="/css/textos-responsivos-com-css-clamp/" target="_blank">tipografia fluida</a>.

Na tipografia fluida, o valor de progresso deve se basear na largura da viewport (`100vw`), em relação a um limite inferior e um superior (`320px` e `1200px`).

Aqui `vw` e `px` estão misturados, então é preciso converter `100vw` para pixels para a conta funcionar em todo lugar.

Para isso, registre a largura da viewport com <a href="/css/property-css/" target="_blank">`@property`</a>, como uma custom property do tipo `<length>` com `100vw` de valor inicial:

```css
@property --vw {
  syntax: "<length>";
  inherits: true;
  initial-value: 100vw;
}

:root {
  --progress: clamp(0, tan(atan2(var(--vw) - 320px, 1200px - 320px)), 1);
}
```

Registrar `--vw` como `<length>` faz o navegador resolver `100vw` para o equivalente em `px`. Agora todos os valores passados para `atan2()` compartilham a mesma unidade, e a conta funciona em todos os navegadores.

![Comparação numa janela de 800px. Sem registro, --vw: 100vw é só um pedaço de texto, var(--vw) vira 100vw e o atan2 recebe vw e px misturados, o que funciona em alguns navegadores e em outros não. Registrada com @property como length, --vw é computada como 800px e o atan2 recebe atan2(480px, 880px), com tan de aproximadamente 0.5455.](/images/blog/css-progress-fallbacks/property-length-resolve-px.webp)

*Sem registro, `var(--vw)` só repassa o texto `100vw`. Registrada como `<length>`, a custom property chega ao `atan2()` já convertida em pixels.*

## A abordagem robusta

E se você quiser usar `rem` nos limites?

Registrar uma custom property como `<length>` resolve o valor para pixels; não dá para forçar a resolução para `rem`. Para não misturar unidades, seria preciso registrar todos os valores, de modo que todos virassem pixels.

Felizmente, isso pode ser otimizado. Não é preciso registrar valor, início e fim separadamente, apenas os dois valores passados para `atan2()`: o offset e o span.

Basta transformar esses valores em custom properties e registrá-los com `@property`:

```css
@property --offset {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

@property --span {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

:root {
  --offset: calc(100vw - 20rem);
  --span: calc(80rem - 20rem);
  --progress: clamp(0, tan(atan2(var(--offset), var(--span))), 1);
}
```

E pronto: a mesma conta de `progress()`, com um suporte muito melhor entre os navegadores.

Para imitar a versão sem limites (*unclamped*) de `progress()`, remova o `clamp()`.

## Qual método usar?

A abordagem depende das unidades em uso:

- **Só números sem unidade:** calcule o valor direto com `calc()` ou `clamp()`.
- **Com unidades:** use o truque do `tan(atan2())`.
- **Uma unidade só:** não é preciso registrar nenhuma custom property.
- **Pixels + uma unidade que não seja pixel:** registre o valor que não está em pixels como `<length>`. É o caso mais comum na tipografia fluida.
- **Várias unidades que não são pixel:** transforme o offset e o span em custom properties, registre as duas como `<length>` e passe-as para `atan2()`.

A função `progress()` já está nos navegadores modernos, então não vai demorar até dar para usá-la nativamente sem preocupação. Até lá, estes fallbacks ajudam a escolher a saída mais simples para cada situação.

---

Fonte: [dpw - desenvolvimento para web](https://dpw.dev/) (pt-BR).
Guia para agentes: https://dpw.dev/llms.txt
Índice completo: https://dpw.dev/sitemap-index.xml
Feed: https://dpw.dev/rss.xml
