Skip to content

Proiecte/Liquid Glass

Liquid Glass

O bibliotecă de componente React Native. Sticla nativă Apple pe iOS 26 și o alternativă în JS și SVG, fidelă până la pixel, peste tot altundeva. O singură componentă, nivelul potrivit pentru fiecare dispozitiv.

ROL
Design și dezvoltare
AN
2025–2026
PLATFORMĂ
Bibliotecă privată
STARE
În producție
Liquid Glass — ParticleRings, o mie cinci sute de orbi, în mișcare

ParticleRings, o mie cinci sute de orbi, în mișcare

05 / 05

Screen 5 of 5: ParticleRings, o mie cinci sute de orbi, în mișcare

Problema

O componentă n-ar trebui să-l pună pe cel care scrie aplicația să se gândească la ce versiune de iOS are utilizatorul.

Pe un iPhone cu iOS 26, un buton de sticlă e chiar materialul Apple și îți urmărește degetul așa cum o face sistemul de operare. Pe un telefon din 2018 e o reconstrucție în JavaScript și SVG pe care aproape nimeni n-o observă ca reconstrucție. Linia pe care o scrie autorul aplicației e aceeași, iar faptul că rămâne aceeași e tot produsul.

Șase din cele optsprezece pachete sunt în Swift și niciunul dintre ele nu e sticla. Sunt părțile care nu pot fi imitate deloc: camera de scanare, decuparea subiectului, recunoașterea de text, Apple Health, widgeturile de pe ecranul principal. Sticla are o alternativă pentru că o alternativă e posibilă. O cameră nu are.

Nu e o demonstrație. Rulează în producție în TeamTipOut și în alte două aplicații, care e singurul motiv pentru care știu ce e de fapt în neregulă cu ea. Fiecare dintre aplicațiile alea a găsit ceva ce o aplicație de previzualizare n-ar fi găsit niciodată.

Toată biblioteca e un singur pariu: că decizia de nivel aparține componentei, nu aplicației. Tot restul decurge de aici.

Rezultatul

În producție în TeamTipOut și în alte două aplicații, dar nepublicată. E biblioteca din care pornesc proiectele clienților, așa că un proiect care o folosește începe de la componente care au trecut deja printr-o verificare de magazin, nu de la un fișier gol. Design și dezvoltare în întregime ale mele.

STARE
În producție
DISTRIBUȚIE
Privată: folosită pe build-urile mele
FOLOSITĂ DIN
2025
Biblioteca

59 de componente.Optsprezece pachete, șase dintre ele native.

Nu am ecrane să-ți arăt, pentru că o bibliotecă nu are. Ce are sunt piese. Astea, în aplicația-galerie în care sunt toate construite.

PACHETE
18
COMPONENTE
59
MODULE NATIVE
6
APLICAȚII ÎN PRODUCȚIE
3

Fundamente

Text · Button · Input · OtpInput · InlineErrorNotice · SectionHeader

Indicatori

AnimatedCheckIndicator · AnimatedRadioIndicator · CheckmarkGlyph · ProgressBar · MetricBar · MetricProgressBar · ScoreRing · SmallProgressRing · SkeletonBlock · AnimatedNumber

Layout și suprafețe

SurfaceCard · NavRow · SeeAllSection · ScreenHeader · EdgeFade · Entrance

Panouri și gesturi

BottomSheet · CollapsingTitle · FloatingSheet · SheetSubmitCta · CloseButton · SwipeableActionRow

Sticlă lichidă

LiquidGlassSurface · LiquidGlassView · LiquidGlassButton · LiquidGlassIconButton · LiquidGlassSwitch · LiquidGlassSlider · LiquidGlassSegmentedControl · LiquidGlassTabBar · LiquidGlassMenu

Mișcare cu Skia

ParticleRings · AmbientDust · CelebrationRing · DotWaveField · ShaderLoadingTrack

Subcăi cu dependențe opționale

LargeScoreRing · RemoteImage

Grafice

BarChart · RadarChart

Serii de obiceiuri

StreakCalendar · StatusRingDayCell · MilestoneBadge · MilestoneTrackSegment · ActivityLegend

Monetizare

PremiumBlur · PremiumSectionLock · PremiumScreenLock

Provideri și efecte la derulare

ThemeProvider · FontProvider · ScrollAwareTopBlur · SkiaReframeImage

Wizard

WizardLayout

Pe dedesubt

Două decizii care merită explicate.Părțile care au durat cel mai mult.

D—01

Nimeni nu întreabă ce iOS e ăsta

Abordarea evidentă e două componente și o verificare de versiune în aplicație. Asta mută problema pe umerii fiecărui autor care folosește biblioteca și garantează că cele două căi se despart în momentul în care una dintre ele primește o corectură.

Așa că biblioteca nu întreabă niciodată pe ce iOS rulează. Întreabă dacă s-a încărcat modulul de sticlă al Apple și apoi dacă modulul acela spune că propriul lui API e disponibil. Un pachet poate fi instalat pe un telefon prea vechi ca să-l ruleze, iar numărul de versiune ți-ar fi spus oricum altceva decât trebuie. Două întrebări, niciun număr de versiune, iar la a doua răspunde Apple, nu eu.

Cache-ul are trei stări, nu două. Undefined înseamnă că n-a întrebat nimeni încă; null înseamnă că întrebarea a fost pusă și răspunsul a fost nu. Fără distincția asta, un dispozitiv fără modul reia un require care eșuează la fiecare randare a fiecărei suprafețe de sticlă de pe ecran.

nativeGlass.tsTS
let resolved: GlassEffectModule | null | undefined; if (resolved !== undefined) return resolved;resolved = null;if (Platform.OS !== 'ios') return resolved; try {  const mod = require('expo-glass-effect');  // installed is not the same as supported  if (mod.isGlassEffectAPIAvailable?.() && mod.GlassView)    resolved = mod;} catch { resolved = null; }

D—02

Ce m-a învățat producția

Un crash pe New Architecture la o valoare fracționară de slider. Un control segmentat care se strica dacă era măsurat înainte ca etichetele lui să termine de așezat. Niciunul nu apare într-o aplicație de previzualizare, pentru că o aplicație de previzualizare nu ține niciodată o valoare venită de la un utilizator real și nu randează sub presiune reală de layout.

Amândouă au fost găsite folosind biblioteca în TeamTipOut. Ăsta e argumentul pentru a-ți trimite propriile unelte în propriul produs. Bug-urile care contează sunt cele care ies la iveală doar când ceva chiar depinde de ele.

CHANGELOG.mdMD
## 0.9.2, found in TeamTipOutfix: clamp slider value before the bridge     0.5 crossed as double, Swift wanted a step ## 0.9.1, found in TeamTipOutfix: defer segmented measure until layout     width read before the font had loaded

D—03

O mie cinci sute de orbi, un singur apel de desenare

Înregistrarea din capul paginii e cea mai grea vedere din bibliotecă și e acolo pentru că o imagine statică nu poate arăta singurul lucru care merită arătat: că rata de cadre se ține.

O mie cinci sute de orbi difuzi e un număr ales ca să fie incomod. Desenat pe calea evidentă înseamnă o mie cinci sute de apeluri de desenare pe cadru și un telefon care se încălzește. Desenat ca un singur atlas grupat, e un singur apel către GPU, iar munca pe cadru devine proporțională cu numărul de orbi, nu cu numărul de pixeli pe care îi acoperă, motiv pentru care roiul poate fi dens fără să fie scump.

Pozițiile se calculează într-un worklet pe firul de UI, așa că un fir de JavaScript ocupat nu poate să sacadeze animația. Fiecare constantă de reglaj stă în JavaScript simplu, în capul fișierului, pentru că un roi se reglează uitându-te la el, iar recompilarea ca să schimbi o rază e felul în care te oprești din a te mai uita.

ParticleRings.tsxTS
const DEFAULT_COUNT = 1500;   // the tuned densityconst ORB_RADIUS_FACTOR = 0.022; // one batched GPU call, not one per orbSkia.drawAtlas(sprite, transforms, colors); // O(orbs), not O(pixels x orbs)useDerivedValue(() => positionsFor(clock.value));

PE CE E CONSTRUIT

LIMBAJ
TypeScript strict · Swift · punți Objective-C
RUNTIME
React Native New Architecture · Fabric · TurboModules
PACHETE
18 pachete · 6 dependențe native opționale
DISTRIBUȚIE
Versionată cu Changesets · CI pe GitHub Actions

CREDITE

Design și dezvoltare în întregime ale mele.


STARE

O bibliotecă privată, care rulează în producție în TeamTipOut și în alte două aplicații. Nu există un repository public către care să trimit.

PROIECTUL URMĂTOR

CoffeeBrew

Un espressor într-o aplicație: împerechere prin Bluetooth, configurare Wi-Fi, preparare programată și un widget pe ecranul principal. Scrisă pe un protocol, nu pe un produs.

Preiau proiecte noi

Spune-mi ce construiești.Un plan scris și un preț fix înainte să se miște vreun ban.

Răspund la tot într-o zi lucrătoare. Dacă nu sunt omul potrivit pentru proiect, ți-o spun din start, nu după trei săptămâni.