Takomillashtirilgan DOM tasvirini olish

Takomillashtirilgan DOM tasvirini olish

Kirish

Bu safar echo xizmati uchun Snap funksiyasini ishlab chiqish vazifasi menga topshirildi. Snap — foydalanuvchi headerdagi kamera belgisini bosgan paytda ekran qanday ko‘rinsa, uni aynan shundayligicha tasvirga oladigan funksiya. Biror narsani faqat so‘z bilan tushuntirish o‘rniga, u ekran skrinshotini ham yuboradi, shuning uchun olingan rasm ham tushuntirish, ham nima noto‘g‘ri bo‘lganining dalili bo‘lib xizmat qiladi.

Bu esa ushbu funksiyaning tezligi va aniqligini ma’lum chegaradan yuqoriga olib chiqishimiz kerakligini anglatardi. Avvalo, tezlik: maqsad taxminan bir soniya edi, chunki ekran tasvirini olish hisobot yuborish jarayonini to‘xtatib qo‘ymasligi kerak. Yana biri — aniqlik: hisobot yuboruvchi ko‘rgan ekran bilan skrinshot bir xil bo‘lishi kerak edi. Agar ular farq qilsa, skrinshot dalil sifatida hech qanday qiymatga ega bo‘lmaydi.

Yaxshilash ustida ilk bor ishlay boshlaganimda, muammo faqat tezlikni oshirish orqali hal bo‘ladi, deb o‘yladim. Tezlikni optimallashtirib, turli skrinshotlar olayotganimda, aniqlik bilan bog‘liq muammo ham borligini aniqladim. Ushbu maqolada shu ikki bosqichni qanday hal qilganim va natijada pipeline bugun qanday ko‘rinishga kelgani jamlangan. Avval natijalarni aytadigan bo‘lsam:

Ekran

Tugunlar soni

Joriy holat

Klon koordinatalarini taqqoslash (aniqlik)

Oddiy ro‘yxat ekrani

1,476

436–457ms

996 tadan 0 tasi

Diagramma ekrani

4,745

1,186ms

4,281 tadan 0 tasi

Yaxshilashlardan oldin 3,3 soniya davom etgan tasvirga olish jarayoni endi yengil ekranda 0,45 soniya, tugunlari uch baravar ko‘p bo‘lgan og‘ir ekranda esa atigi 1,2 soniya davom etadi. Aniqlik ham yaxshilandi: og‘ir ekranda barcha 4 281 ta element koordinatalari endi haqiqiy ekran bilan to‘liq mos keladi.

DOM tasvirini olishning cheklovlari

Muammoning ildizi Snap haqiqiy skrinshot olmayotganida edi. html-to-image kabi kutubxonalar veb-sahifa amalda render qilingan piksellarga kira olmaydi, shuning uchun amalda ular quyidagicha ishlaydi:

① Ekranning butun DOM daraxtidan nusxa olish

② Har bir elementga hozir qo‘llanayotgan CSS qiymatlarini birma-bir inline uslublar sifatida qo‘llash

③ Natijani SVG ichidagi <foreignObject> ga joylashtirish

④ Ushbu SVG ni <img> sifatida yuklash va canvas ustiga chizish

Boshqacha aytganda, bu brauzerga xuddi shu HTML kodini yana bir marta render qildirishga o‘xshaydi. Muammo shundaki, ④-bosqichdagi <img> ichidagi SVG asl sahifadan butunlay ajratilgan. Bu xavfsizlik sabablari tufayli shunday ishlab chiqilgan va ushbu izolyatsiyaning uchta xususiyati bor; keyinchalik ularning har biri haqiqiy xatoga aylandi.

  • U ota-ona hujjatidagi veb-shriftlardan foydalana olmaydi. Agar ular ichiga joylashtirilmagan bo‘lsa, tizim shriftlari bilan almashtiriladi, natijada belgilar eni o‘zgarib, maket siljiydi.

  • Tarmoqdan foydalanish bloklangan. Rasm URL manzillarini to‘g‘ridan-to‘g‘ri olib bo‘lmagani uchun, tasvirga olishdan oldin ularning barchasi data URI ga (fayl tarkibi satrga aylantirilib, shu satr ichiga joylashtiriladigan shakl) o‘zgartirilishi kerak.

  • Skriptlarni bajarish o‘chirilgan. JavaScript bajarilgan yoki bajarilmaganiga qarab xatti-harakati o‘zgaradigan elementlar faqat tasvirga olish jarayonida boshqacha render qilinadi.

Ishlab chiqish jarayoni 1 — Sekinlik sababini aniqlash va to‘rtta kutubxonani sinab ko‘rish

Men olgan birinchi so‘rov: "Skrinshot yaratish biroz uzoq davom etayotganga o‘xshaydi." O‘lchab ko‘rganimda, dialog darhol ochilardi, biroq skrinshot tayyor bo‘lguncha spinner uch soniyadan ko‘proq aylanib turardi. Shu vaqt davomida asosiy oqim butunlay to‘xtab qolardi, hatto bosish amallarini ham qayta ishlab bo‘lmasdi. Brauzerdagi JavaScript bir vaqtning o‘zida faqat bitta vazifani bajaradigan bir oqimli tuzilmadan foydalanadi, shuning uchun tasvirga olish jarayoni ushbu oqimni band qilganda, ekran qotib qolgandek ko‘rinadi.

Har bir bosqichda to‘siqlar qayerda yuzaga kelishini tekshirish

Avvaliga kechikishga ekrandagi elementlarning ko‘pligi sabab bo‘lyapti, deb o‘yladim. Shuning uchun jarayonni bosqichlarga ajratib, har biri qancha vaqt olishini o‘lchashdan boshladim. O‘lchovlar omillardan biri seriyalashtirilgan SVG hajmi ekanini ko‘rsatdi. Tahlil natijasida har bir tugunga taxminan 40KB inline uslublar biriktirilgani ma’lum bo‘ldi. Yana bir omil cssText edi: manba kodidan ushbu qiymat bo‘sh bo‘lganda, kutubxona barcha uslublarni bir yo‘la ko‘chiradigan tezkor yo‘l o‘rniga xususiyatlarni birma-bir ko‘chiradigan yo‘ldan foydalanishini tasdiqladim.

Haqiqiy ro‘yxatni ajratib olganimda, ilovada taxminan 1 000 ta hisoblangan qiymat xususiyati borligini, ularning yarmi CSS maxsus xususiyatlari ekanini aniqladim. Bular mavzu ranglari yoki ikonka yo‘llarini o‘z ichiga olgan, -- bilan boshlanuvchi o‘zgaruvchilar edi. Biroq hisoblangan qiymat o‘zgaruvchilar allaqachon almashtirilgandan keyingi yakuniy qiymatni qaytargani sababli, o‘zgaruvchilarning o‘zini klonga yana kiritishning hojati yo‘q edi. Ko‘rinishga aloqasi bo‘lmagan xususiyatlarni ham olib tashlagach, ro‘yxatni taxminan 100 ta elementgacha qisqartirdim.

// utils/inlineStyleProperties.ts — 인라인할 CSS 프로퍼티 127개
export const INLINE_STYLE_PROPERTIES = [
  'display', 'position', 'top', 'right', 'bottom', 'left', /* ... */

  // 'box-sizing' 은 반드시 있어야 합니다. Chrome의 computed width 는 그 요소의
  // box-sizing 기준값이라, 빼면 클론이 padding+border 만큼 넓어집니다.
  'width', 'height', 'box-sizing', /* ... */

  // ::before / ::after 규칙도 이 목록으로 만들어집니다.
  // 빠지면 의사요소(구분선·체크박스·화살표)가 통째로 사라집니다.
  'content',

  // 아이콘 대부분이 mask 로 렌더됩니다. 빠지면 아이콘이 전부 사라집니다.
  'mask-image', 'mask-size', '-webkit-mask-image', /* ... */
];

1-kod. Ro‘yxatdan biror elementni olib tashlash xatoga sabab bo‘lmaydi; u faqat skrinshotning sezdirmay farq qilishiga olib keladi. Shu sababli sababini izohda qoldirdim.

Ushbu ro‘yxatdagi eng xavotirli element content edi. Agar u chiqarib tashlansa, ::before / ::after yordamida chizilgan har bir element bo‘sh holda render qilinardi. Buni testlar orqali aniqlamadim; kutubxona manba kodini o‘qiyotganimda tasodifan topib qoldim. Bu bosqichdagi eng katta zaiflik ro‘yxatni tuzish bilan birga, aslida nimalar kerakligini tekshirish usulini ham belgilab qo‘ymaganim edi. Keyinchalik boshqa kutubxonalarni baholashni boshlashimga bevosita sabab ham shu bo‘ldi.

To‘rtta yondashuvning har birini amalga oshirdim va taqqosladim

AI yordamida ajratib olingan ro‘yxat tabiatan xavfli. Snap bir nechta epizodlarda foydalanish uchun mo‘ljallangan umumiy kutubxonaning bir qismi, biroq men ro‘yxatni atigi bir nechta ekran asosida tuzdim. Agar boshqa jamoaning ilovasida ro‘yxatga kiritilmagan xususiyatdan foydalanilsa, skrinshot xatosiz, ammo sezdirmay farq qiladi. Xatolar haqida hisobot berish vositasida sezdirmay noto‘g‘ri natija sekin natijadan yomonroq, deb hisoblaganim uchun, qo‘lda saralashni talab qilmaydigan muqobillarni sinab ko‘rdim.

Yondashuv

Usul

Yengil ekran

Og‘ir ekran

O‘sish koeffitsiyenti

A-variant

html-to-image + AI yordamida ajratilgan CSS xususiyatlari

378–388ms

828–865ms

2,2×

B-variant

modern-screenshot + ish vaqtida yaratiladigan CSS xususiyatlari

575–863ms

1 870–2 045ms

2.8×

C varianti

snapdom + klasslarni deduplikatsiya qilish

1 235–1 985 ms

5 186 ms (birinchi ishga tushirishda 26 soniya)

4.2×

D varianti

html2canvas — CSSʼni JS ichida talqin qiladi va uni bevosita chizadi

516–610 ms

(O‘lchash shartlari boshqacha)

Ushbu jadvaldagi qarorni aynan oxirgi ustun belgiladi. Tugunlar soni 4,3 baravar oshganda, o‘sish sur’atlarining o‘zi turlicha bo‘ldi. Har bir tugunda o‘qiladigan va yoziladigan xususiyatlar soni A variantida taxminan 100 ta, B variantida esa taxminan 450 ta bo‘lgani uchun ekran og‘irlashgani sari tafovutning kengayishi muqarrar edi. Agar faqat yengil ekranlarni taqqoslaganimizda, B variantini tanlagan bo‘lardik.

  • B varianti (modern-screenshot) — Faqat kutubxonani almashtirish hech qanday yaxshilanish bermadi. U har bir teg uchun faqat standart qiymatlardan farq qiladigan qiymatlarni inline qilish orqali chiqishni kichraytiradi, ammo qaysi qiymatlar farq qilishini aniqlash uchun oxir-oqibat hammasini o‘qishga to‘g‘ri keladi. Ishlash vaqtida yaratilgan ro‘yxatni uzatish vaqtni 806 ms gacha qisqartirdi, ammo og‘ir ekranlarda bu vaqt ikki soniyadan oshdi.

  • C varianti (snapdom) — Bir xil stillarni klasslarga guruhlagani uchun u eng kichik markupni hosil qildi va render aniqligi uch variant ichida yagona mukammal natija bo‘ldi. Biroq faqat shriftning bir qismini qo‘shish uchun ochiq parametr mavjud emas edi, shuning uchun ilovaning butun shriftini ham joylashtirishga to‘g‘ri keldi. Natijada og‘ir ekranda birinchi suratga olish 26 soniya davom etdi.

  • D varianti (html2canvas) — U keng qo‘llanadigan kutubxona bo‘lgani uchun uni sinab ko‘rdik. U boshqa toifaga mansub bo‘lgani (CSSʼni JS ichida talqin qilib, uni bevosita canvasʼga chizadi) sababli shriftni joylashtirish umuman kerak emasligi jozibador tuyuldi. Biroq ilova ikonalarining aksariyati foydalanadigan mask-image qo‘llab-quvvatlashi bundle’da butunlay yo‘q edi, shuning uchun sarlavha va asboblar panelidagi yettita ikonkaning barchasi qora kvadrat sifatida chizildi. Uning so‘nggi relizi 2022-yilda chiqqan bo‘lib, ba’zan zamonaviy CSS funksiyasiga duch kelib, istisno yuzaga keltirar va ishdan chiqardi.

Nega dastlab ishlatib kelgan kutubxonamizni saqlab qoldik

Xulosa A variantini saqlab qolishdan iborat bo‘ldi. U og‘ir ekranlarda bir soniyalik talabga javob bergan yagona variant edi. Bundan tashqari, snap-view qaysi ekranlarda ishlatilishini bila olmaganimiz sababli, o‘lchagan ekranlarimizda zo‘rg‘a talabga javob berishning o‘zi yetarli emas, degan qarorga keldik.

B variantini qo‘llab-quvvatlovchi dalil shundan iborat ediki, "kutilmagan CSS bilan kuratsiyasiz ishlash xavfsizroq", ammo oq ro‘yxatni yaratish vaqtida tekshirmagan ekranlarda olingan ikkita suratni pikselma-piksel taqqoslaganimizda, taxminan 10% farqni aniqladik. Avvaliga A variantida haqiqatan ham nimadir yetishmayapti, deb o‘yladim, ammo ularning har birini ekrandagi haqiqiy koordinatalar bilan tekshirganimda, ikkalasi ham bir xil tarzda noto‘g‘ri ekanligi ma’lum bo‘ldi. Farq birortasi aniqroq ekanida emas, balki ikkala kutubxona bir xil CSSʼni biroz boshqacha chizganida edi. Tasdiqlanmagan foyda uchun 2,2 baravar sekinroq variantni tanlay olmasligimizga qaror qildik. Shunga qaramay, bu "A varianti xavfsizroq" ekanini isbotlamaydi. Bu faqat muammo tekshirilgan ikki ekranda kuzatilmaganini anglatadi, shuning uchun ushbu zaiflik hamon mavjud.

D varianti bilan o‘tkazilgan tajriba bizga yana bir qimmatli saboq berdi. html2canvas ishlamay qolganda, sahifada iframe qoldiradi, ammo querySelectorAll('*') uning ichini ko‘ra olmaydi. Ulardan beshtasi to‘planib qolgan holda A variantini qayta ishga tushirganimizda, markup 66 MB gacha kattalashdi va suratga olish vaqti 4 960 ms gacha oshib ketdi. Yagona ishora bazaviy ko‘rsatkichdan 13 baravar chetlanish edi; agar bu raqamga o‘z holicha ishonganimizda, "A varianti bu ekranda besh soniya vaqt oladi" degan mutlaqo noto‘g‘ri hujjatni tayyorlagan bo‘lardik.

Ishlab chiqish jarayoni ② — Tezlashtirgandan so‘ng suratlar ekrandan farq qilishini aniqladik

Tezlik muammosini hal qilganimizdan so‘ng, suratlarni haqiqiy ekran yoniga joylashtirdik va to‘rtta nomuvofiqlikni aniqladik. Ularning barchasining sababi dastlabki taxminlarimizdan farq qilardi. Ayniqsa, 2-, 3- va 4-muammolarning barchasi shrift bilan bog‘liq, deb gumon qilgandik, ammo aslida ularning shriftlarga umuman aloqasi yo‘q edi.

#

Alomat

Haqiqiy sabab

Toifa

1

ag-grid katakchalari va saralash strelkalari butunlay yo‘qolgan

Pseudo-element fonlari kutubxonaning resurslarni inline qilish yo‘li bilan qamrab olinmagan

Resurs

2

Matn kattaroq ko‘rinib, joyidan siljigan

pixelRatio: 1 CSS piksellariga asoslanadi (foydalanuvchi masshtabi 90% bo‘lganda)

Masshtab

3

Sarlavhadagi breadcrumb ikki qatorga o‘ralgan

0.007px qiymatidagi o‘nlik yaxlitlash flex-wrap holatini o‘zgartirgan

Joylashuv

4

Sarlavha ostida bo‘sh tasma paydo bo‘lgan

<noscript> faqat suratga olish ichida qayta faollashgan

Joylashuv

Yo‘qolgan ikonalar

Qator tanlash katakchalari va ustun saralash strelkalari butunlay yo‘qoldi, ammo faqat suratga olishda. Shu bilan birga, sarlavhadagi kamera va bildirishnoma ikonkalari joyida edi. Bir xil ikonalar nega turlicha ishlaganini bilmasdik, shuning uchun kutubxona manba kodini o‘rgandik va resurslar data URIʼlarga aylantiriladigan atigi ikkita joy borligini aniqladik: elementning o‘z fonini o‘qiydigan yo‘l va <img> teglarini o‘qiydigan yo‘l. Biroq pseudo-element qoidalari <style> matni sifatida yaratiladi va biriktiriladi, shuning uchun ular ikkala yo‘l bilan ham qamrab olinmagan. Muammoli CSS host ilovaning SCSSʼida bo‘lgani uchun uni o‘zimiz ham tuzata olmasdik.

Ishora kutilmagan joyda edi. Biz faqat shriftlarni joylashtirish uchun ishlatib kelgan fontEmbedCSS parametrining manba kodini ko‘rib chiqarkanmiz, u qabul qilingan satrni o‘zgarishsiz olib, undan <style> yaratishi va uni klonning eng old qismiga kiritishini aniqladik. Nomiga qaramay, u aslida ixtiyoriy CSS kiritish nuqtasi edi.

const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : ...
if (cssText) {
  const styleNode = document.createElement('style')
  styleNode.appendChild(document.createTextNode(cssText))
  clonedNode.insertBefore(styleNode, clonedNode.firstChild)
}

2-kod. Shu bir qatorni tekshirish "tuzatish yo‘li yo‘q" deb hujjatlashtirgan muammoni hal qildi.

Shunday qilib, suratga olishdan darhol oldin har bir elementning pseudo-elementlarini skanerladik, faqat url() ishlatadiganlarini tanladik, tegishli rasmlarni data URIʼlarga aylantirdik, nishon elementlariga marker atributini qo‘shdik va shu markerlarga yo‘naltirilgan CSS qoidalarini uzatdik. Ikki jihat muhim edi. !important zarur bo‘ldi, chunki kutubxona yaratgan qoidalar keyinroq kiritiladi va shu sababli hujjat tartibi bo‘yicha ustun keladi. Shuningdek, bir xil elementlardagi deklaratsiyalar bir xil marker raqamlarini bo‘lishadigan qilindi; aks holda har bir ag-grid qatori bir xil katakcha tasviri ma’lumotlarini har bir qator uchun alohida nusxalab, SVG hajmini keskin oshirardi.

Bu yerda taxminlarimizdan biri rad etildi. Stillar jadvallarini tahlil qilish har bir elementni skanerlashdan arzonroq bo‘ladi, deb o‘ylab, avval shu yondashuvni qurdik, ammo amaldagi o‘lchovlar stillar jadvalini skanerlash yondashuvi har bir elementni aylanib chiqishdan ancha sekinroq ekanini ko‘rsatdi. getComputedStyle kutilganidan arzonroq, stillar jadvallarini skanerlash esa qimmatga tushdi. To‘g‘ridan-to‘g‘ri aylanib chiqishga o‘tish kodni qisqartirdi va suratga olish vaqtini 348 ms dan 332 ms gacha kamaytirdi.

Ekrandan kattaroq suratga olishlar

Bir nechta ekranni suratga olayotganimizda, sarlavhadagi matn kattaroq ko‘rinib, yon tomonga siljiganini payqadik. Joylashuv buzilganga o‘xshardi, ammo aslida bu masshtablash muammosi edi. Ekran har bir CSS pikseli uchun devicePixelRatio ta fizik piksel bilan chiziladi, ammo parametr pixelRatio: 1 qiymatida qotirib qo‘yilgan edi. Men brauzer masshtabini 90% qilib ishlatardim, bu esa DPRʼni 0.9 ga tushirgan va surat 1,112 baravar kattalashgan (1÷0.9).

const capturePixelRatio = (): number =>
  Math.min(window.devicePixelRatio || 1, 2);

3-kod. DPR 2 muhitida suratga olish vaqti deyarli o‘zgarmadi; faqat hajm oshdi (120KB → 316KB).

Sarlavha ostidagi bo‘sh tasma

Faqat tasvirga olishda sarlavhaning bevosita ostida balandligi 24px bo‘lgan bo‘sh tasma paydo bo‘ldi va uning ostidagi barcha kontent pastga siljidi. Biroq klon maketining koordinatalarini qayta o‘lchaganimda, ular mukammal mos keldi. Agar DOM bosqichida margin bo‘lmagan bo‘lsa, qolgan yagona ehtimol rasterizatsiya bosqichi edi. Shuning uchun tasvirga olingan rasm piksellarini bevosita skanerlab, “siyoh mavjud bo‘lgan y diapazonlari”ni ajratib oldim. Faqat sarlavha o‘zining dastlabki joyida edi; qolgan barcha narsa aynan 24px pastga siljigan. U jamlanib emas, faqat bir marta siljiganligi, oqimning eng boshidagi biror narsa bitta qator egallaganini anglatardi.

Aybdor <noscript> edi. HTML spetsifikatsiyasiga ko‘ra, brauzerning “buni yashirish” standart qoidasi faqat skriptlar yoqilganida qo‘llanadi. Biroq avval aytib o‘tilganidek, <img> ichidagi SVG skriptlar o‘chirilgan kontekstda tasvirlanadi. Shu sababli bu teg faqat tasvirga olish paytida yana faollashib, bitta qatorni egallagan. Sarlavhaga ta’sir qilinmaganining sababi shundaki, position: fixed uni oqimdan tashqariga joylashtiradi. Shu bois u “sarlavha ostidagi bo‘sh tasma” kabi ko‘ringan. Bu Chrome xatosi emas, spetsifikatsiyaga muvofiq xatti-harakat edi.

const EXCLUDED_TAG_NAMES = new Set(['NOSCRIPT', 'SCRIPT']);

filter: (node) => {
  if (!(node instanceof Element)) return true;
  if (EXCLUDED_TAG_NAMES.has(node.tagName)) return false;
  return !EXCLUDED_CLASS_NAMES.some((name) => node.classList?.contains(name));
},

6-kod. Tuzatishdan so‘ng tab osti chiziqlari va jadval sarlavhasi chegaralari kabi hatto 1px li chiziqlar ham o‘z joyiga qaytdi.

Oxir-oqibat, asosiy kalit diagnostika vositalari bo‘ldi

Ortga nazar tashlasam, bu vazifada amalda qilgan ishlarimizning aksariyati vositalar yaratishdan iborat bo‘lgan. Skrinshotlarni ko‘z bilan taqqoslaganimizda, to‘rtta muammodan bittasini ham aniqlay olmadik.

  • 1-qadam · Koordinatalarni qo‘lda yaratilgan klon bilan taqqoslash — 996 ta tugundan faqat 1 tasi mos kelmadi (animatsiya paytida). Bu yerda hech narsani aniqlay olmadik.

  • 2-qadam · Kutubxona tomonidan amalda yaratilgan SVGni ajratib olish va taqqoslash — Bu bosqichda psevdoelement uslublari va filtrlarining barchasi aks etgan edi. Flex qatorining o‘ralishini aynan shu yerda aniqladik.

  • 3-qadam · Tasvirga olingan rasm piksellarini bevosita skanerlash — Har bir qatorda siyoh bor-yo‘qligini hisobladik va kontent hududlarining chegaralarini ajratib oldik. <noscript>ni aynan shu yerda aniqladik.

Menimcha, 2-qadamning yetarli bo‘lmaganligi butun bu vazifaning asosiy sabog‘idir. Taqqoslash uchun ishlatilgan iframe’da skriptlar yoqilgan, shuning uchun <noscript> yashiriladi. Natijada koordinatalar mukammal mos kelgan, faqat haqiqiy tasvir noto‘g‘ri joylashgan edi. Vositalarni yana bir bosqich rivojlantirmagunimizcha, DOM darajasidagi koordinata taqqoslashlari rasterizatsiya bosqichidagi muammolarni aniqlay olmasligini anglamadik.

Joriy tasvirga olish konveyeri

Yuqoridagi barcha qarorlar va tuzatishlar qo‘llangan joriy kod shu. Unda ekranni vaqtincha o‘zgartirib, so‘ng qayta tiklaydigan uchta mantiqiy qism mavjud. Har bir qadamning sababini tushuntiruvchi izohlarni aynan shu tartibda qoldirdim.

export const captureViewport = async (): Promise<string | null> => {
  try {
    // 퀵메뉴 닫힘 등 직전 DOM 변경이 화면에 반영된 뒤 캡처합니다.
    await new Promise((r) =>
      requestAnimationFrame(() => requestAnimationFrame(r)));

    // 의사요소 배경을 data URI 로. pinScrollOffsets 보다 먼저
    const { css: pseudoCss, restore: restorePseudo } =
      await inlinePseudoBackgrounds();
    // 현재 라인을 파악해야 하므로 스크롤 처리보다 먼저 진행
    const unpinFlexLines = pinFlexLines();
    // scrollTop 은 CSS 가 아니라 런타임 상태여서 복제본이 찾지 못함
    //  transform 으로 번역해 두면 복제본이 그대로 복사함
    const unpinScrollOffsets = pinScrollOffsets();

    let result: string | null;
    try {
      const shot = toJpeg(document.body, {
        ...CAPTURE_OPTIONS,               // 화이트리스트
        pixelRatio: capturePixelRatio(),  // 실 기기 픽셀 기준
        // foreignObject 는 웹폰트에도 네트워크에도 접근할 수 없으므로 필요한 것은 전부 여기에 넣음
        fontEmbedCSS: CAPTURE_FONT_EMBED_CSS + GLOBAL_CAPTURE_CSS + pseudoCss,
      });
      const timeout = new Promise<null>((r) => setTimeout(r, TIMEOUT_MS, null));
      result = await Promise.race([shot, timeout]);
    } finally {
      // 복구는 반드시 역순. 순서를 바꾸면 스크롤 값이 엉뚱하게 잡힘
      unpinScrollOffsets();
      unpinFlexLines();
      restorePseudo();
    }
    return result;   // 실패·타임아웃이면 null — 제보 흐름을 막지 않음
  } catch (e) {
    console.warn('[captureViewport] failed', e);
    return null;
  }
};

7-kod. Tasvirga olishni boshlash nuqtasi.

Bu yerda pinScrollOffsets haqida biroz batafsilroq tushuntiraman. Bu ilova mainning ichki konteyneri butun brauzer oynasi emas, balki shu konteynerning o‘zi aylanishi uchun tuzilgan. Biroq aylanish holati CSS emas, ish vaqtida saqlanadigan holat bo‘lgani uchun klonda u 0 ga aylanadi. Natijada foydalanuvchi ekran o‘rtasini ko‘rayotganda muammo haqida xabar bergan bo‘lsa ham, tasvirga olishda yuqori qism aks etadi. Uchala kutubxonada ham muammo bir xil bo‘ldi, hatto mos opsiyalarga ega bo‘lgan ikkitasida ham bu tuzilma bilan ular samara bermadi.

Yechim aylanishni CSSga o‘girish edi. Aylantirilgan konteynerning farzand elementlarini aylanish miqdoriga teng masofaga transform: translate() yordamida siljitamiz va bir vaqtning o‘zida konteynerning aylanish holatini 0 ga qaytaramiz. Bu ikki o‘zgarish bir-birini bekor qilgani uchun haqiqiy ekran bir pikselga ham siljimaydi, biroq transform hisoblangan uslubning bir qismi bo‘lgani sababli klon uni o‘zgartirmay nusxalaydi. Buning yon ta’siri sifatida sticky/fixed elementlar o‘z joyidan siljiydi, shuning uchun siljish miqdorini o‘lchab, teskari yo‘nalishda tuzatdik. Tuzatishdan oldin 96 ta element noto‘g‘ri joylashgan edi; keyin bu son 1 taga tushdi.

Hali qolayotgan masalalar

Eng katta muammo — whitelist hali ham qo‘lda yuritilishi kerak. Ro‘yxatga kiritilmagan xususiyatlar xatolik chiqarmasdan, brauzerning standart qiymatlari bilan tasvirlanadi. Bundan tashqari, kutubxona integratsiya qilingan har bir host alohida repository bo‘lib, ro‘yxat tarqatiladigan paket ichida joylashgan. Shu sababli boshqa jamoa muammoni aniqlagan taqdirda ham, ular bizga xabar berib, reliz chiqishini kutishlari kerak. Joriy ro‘yxat minimal darajada saqlangan, shuning uchun vizual natijaga aloqasi bo‘lmagan xususiyatlarni chiqarib tashlash orqali uni taxminan 300 ta yozuvgacha kengaytirish imkoniyati bor. Bir soniyalik byudjet doirasida optimal nuqtani topishni rejalashtiryapmiz.

Ikkinchi muammo — validatsiyani himoya qiladigan mexanizm yo‘q. Avval yaratgan koordinata taqqoslash va piksel skanerlash vositalari avtomatlashtirilgan testlar emas, balki zaruratga qarab yaratilgan va ishlatilgan vositalar edi. Kontentni deyarli tushirib qoldirgan vaziyatni bugun ham aynan o‘sha tarzda qayta yuzaga keltirish mumkin. Tasvirga olish natijalarini taqqoslaydigan avtomatlashtirilgan test qo‘shishni keyingi vazifa deb hisoblaymiz.

Uchinchi muammo — validatsiya qamrovi. Hozirgacha biz faqat ikki xil ekran — jadval ro‘yxati ekrani va diagramma ekranini o‘lchadik. Boshqa ekranlarda qanday CSS ishlatilishini bilmaymiz, asosiy tuzilma esa o‘zgarmagan: agar tasvirga olish sezdirmasdan noto‘g‘ri joylashsa, bundan hech kim xabar topmaydi.

Shu sababli foydalanuvchilar avtomatik tasvirga olish o‘rniga o‘zlari olgan fotosuratlarni yuklashlari uchun mavjud yo‘lni ochiq qoldirdik. Avtomatik tasvirga olish “foydalanuvchi hech narsa qilishi shart bo‘lmagan holda sukut bo‘yicha biriktiriladigan qulaylik funksiyasi” sifatida qaraladi, biroq kerakli sahnani tasvirga olish muvaffaqiyatsiz bo‘lsa, foydalanuvchilar rasmni o‘zlari biriktirishlari mumkin. Biroq bu tub yechimdan ko‘ra ko‘proq zaxira choraga o‘xshaydi. Tasvirga olish nishonini butun ekrandan foydalanuvchi ko‘rsatgan hududgacha toraytirish yoki brauzerning ekran tasvirini olish API’sidan foydalanish kabi boshqa ehtimoliy yaxshilanishlarni ham ko‘rib chiqishda davom etamiz.

Xulosa

Bu vazifa ustida ishlash davomida anglaganim shuki, aslida bu ikki xil ish turi edi. Tezlik masalasi nimaning qimmatga tushayotganini topishdan iborat edi va javobni raqamlar berdi. Aniqlik esa farq nimada ekanini ko‘rish imkonini yaratish masalasi bo‘lib, javobni topishdan oldin vositalarni yaratishimiz kerak edi. Har safar bir daraja yuqorilaganimizda — vizual tekshiruvdan koordinatalarga, koordinatalardan piksellarga o‘tganimizda — yana bitta muammoni aniqladik.

Bu kutubxonalarni faqat hujjatlarini o‘qib o‘rganishga uringanda ishlatish ko‘pincha qiyinlashdi. includeStyleProperties faqat muayyan tarmoqda ishlatiladi, shuning uchun avvaliga men uni “Chrome’da e’tiborsiz qoldiriladi” deb noto‘g‘ri xulosa qildim. Nomidan farqli ravishda, fontEmbedCSS ixtiyoriy CSS kiritish nuqtasi edi, restoreScrollPosition va clip: 'viewport' kabi nomi aynan mos ko‘ringan opsiyalar esa bu tuzilma bilan hech qanday ta’sir ko‘rsatmadi. Boshi berk ko‘chaga kirib qolganda, eng tezkor nosozliklarni tuzatish usuli avval manba kodida opsiya qayerda ishlatilganini topish bo‘ldi.

Hali ham ayrim afsuslarim bor. Whitelistni yaratganimiz bilan bir vaqtda uni majburiy qo‘llaydigan validatsiyani tuzmadik, shuningdek, unumdorlik ustida ishlayotgan paytimizda aniqlik muammolarini tekshirmadik va ularni faqat keyinroq aniqladik. Shunga qaramay, bu tajriba tufayli endi kutubxonadan qanday foydalanishni bilish bilangina cheklanmayman: brauzer ekranni qanday chizishini va bu natija yana rasmga aylantirilganda nimalar yo‘qolishini yaxshiroq tushunaman.

Ma’lumotnoma hujjatlari

Owler

Site footer