THE LEDGER ABD SHANTI
CITATION STRENGTH0%
SOURCE #020 VERIFIED · LIVE AUG 13, 20266 MIN READWAR STORY PART OF RECOVERY

An 8.7-Second Lie: How One Animation Killed My Mobile Score

¶ WAR STORYTHE NUMBER THAT MADE NO SENSE

01Eight point seven seconds for text that was already there

The report said mobile Largest Contentful Paint was 8.7 seconds. I did not believe it. The page was a static file on a fast server, the headline was plain text in the HTML, there was no font swap and no hero image to blame. I had watched it appear instantly on my own phone.

That is the part that took me two days to understand. It did appear instantly. To me.

My LCP was not slow. It was hiding.

What the browser was actually measuring

The headline started at opacity zero and faded in after a delay. To a person that reads as a nice entrance. To the browser it reads as an element that is not painted yet, so the clock keeps running, and Largest Contentful Paint gets recorded at the moment the text finally becomes visible rather than the moment it arrived.

The server had done its job in a few hundred milliseconds. My stylesheet then sat on the result and refused to show it. The browser does not time when your content arrives. It times when your content becomes visible.

¶ PLAYBOOKTHE ONE LINE

02Animate movement, never visibility

The fix is smaller than the bug deserved. Keep the entrance, drop the fade. Move the element with transform and leave opacity alone, and the text is painted at full strength on the first frame while still sliding into place.

// THE SAME ENTRANCE, TWO WAYS TO WRITE IT
What I shippedLCP 8.7s
.hero h1 {
  opacity: 0;
  animation: rise .8s ease
             .6s backwards;
}
@keyframes rise {
  from { opacity: 0;
         transform: translateY(24px) }
  to   { opacity: 1;
         transform: none }
}
What I should have shippedLCP as served
.hero h1 {
  /* no opacity, ever */
  animation: rise .8s ease both;
}
@keyframes rise {
  from { transform: translateY(24px) }
  to   { transform: none }
}


Same look to a person · same markup · same server

Why the fixed version is also smoother

The second version is not a compromise. Transform is handled on the compositor, so the animation runs without repainting anything, which is the same reason it does not disturb the paint timing. The broken version was asking the browser to withhold content and do more work at the same time.

If an animation decides whether text is visible, it has stopped being decoration and started being a performance budget.

The family of bugs this belongs to

Once you know the shape, you find it everywhere. A fade up on a hero. A scroll reveal that starts every section at zero opacity. A skeleton loader that covers real content that already shipped. A cookie banner that paints over the headline. Each of them takes content the server delivered on time and hides it from the only clock that counts.

EXTRACTED — THE SENTENCE THIS ENTRY EXISTS FOR
The server was fast. My stylesheet was the slow part.
¶ PLAYBOOKRULES THAT SURVIVE A REDESIGN

03Five rules I now apply before any animation ships

Vitals are not only a ranking input. A slow first paint costs you the machines as well, because a fetcher on a clock takes what it has when time runs out, which is the same failure described in the case for shipping the words in the document. The agents that will or will not wait for you are already named in your log.

The rules

[1]

Nothing above the fold starts invisible. Not the headline, not the subhead, not the first paragraph. If it is the biggest thing on the screen, it must be painted the moment it arrives.

[2]

Entrances move, they do not appear. Transform for the motion, no opacity in the keyframes for anything in the first viewport. Below the fold you can fade whatever you like.

[3]

Delays are the multiplier. A fade is survivable. A fade that waits six hundred milliseconds first is how a fast server produces an eight second number.

[4]

Test on the throttled device, not yours. This bug is invisible on a fast phone on office wifi, which is exactly the machine every developer checks it on.

[5]

Re-measure after every redesign. Nobody reintroduces this on purpose. It comes back when a component gets restyled by someone who never saw the original number.

What it cost and what it taught

Two days of looking at the server, the font loading and the image pipeline, none of which were the problem, because I was searching for something slow and the actual fault was something hidden. When a number is impossible, stop optimising and start asking what the number is really measuring.

The animation is still there. It still slides. Nobody who visits the page could tell you which version they are looking at, which is the most annoying part of the whole story and also the point. The visible result was never the thing that needed to change, only the property doing the moving.

Tomorrow: how a model decides which brands to name, from a year of watching it happen.

// QUICK ANSWERS
>Why is my LCP slow when my server is fast?+
Because Largest Contentful Paint measures when your biggest element becomes visible, not when it arrives. A headline that starts at opacity zero and fades in is not painted until the fade runs, so a page served in milliseconds can report eight seconds. Check your CSS animations before you touch the server.
>Do CSS animations affect Core Web Vitals?+
Only the ones that control visibility. Animating opacity on a large element delays the paint the browser records, while animating transform does not, because the element is painted at full strength and merely moved. Keep entrances to transform for anything in the first viewport.
>How do I fix a slow LCP caused by an animation?+
Remove opacity from the keyframes and animate transform instead, and delete any animation delay on above the fold content. The entrance looks identical to a visitor and the element is now painted on the first frame, so the recorded time falls back to whatever your server was already delivering.
Abd Shanti, author of CITED
VERIFIED HUMAN
Abd Shanti
GEO EXPERT · THE AUTHOR
// CITE THIS ENTRY
Abd Shanti. "An 8.7-Second Lie: How One Animation Killed My Mobile Score" CITED, Entry 020, Aug 13 2026. unknown.ps/blog/the-8-second-lie/
// RELATED ENTRIES
QUOTE COPIED — CITE FREELY