Fallbacks para a função CSS progress() em todos os navegadores
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.
A nova função CSS progress() 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:
progresso = (valor atual - início) / (fim - início)Isso também é conhecido como normalização.

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 (como no artigo de Preethi Sam), 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:
--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:
--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 truque do tan(atan2()).
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:
--progress: tan(atan2(500px - 320px, 1200px - 320px));
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 tipografia fluida.
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 @property, como uma custom property do tipo <length> com 100vw de valor inicial:
@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.

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:
@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()ouclamp(). - 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 paraatan2().
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.