SnapDOM: كيف تحوّل أيّ عنصر في صفحة الويب إلى صورة داخل المتصفّح

تاريخ النشر: وقت القراءة:
للقراءة
عدد الكلمات:
كلمة

تحويل عنصرٍ من صفحة ويب إلى صورة — بطاقةٍ أو مخطّطٍ أو تقرير — مسألةٌ أعقد ممّا تبدو. فالمتصفّح يرسم هذا العنصر على الشاشة عشرات المرّات في الثانية، ويعرف تماماً أين تقع حدوده وما ألوانه وكيف تتراكب طبقاته، ومع ذلك لا يعرض للجافاسكربت أيّ واجهةٍ تقول له: أعطِني صورةً لهذا العنصر. القدرة موجودة داخل محرّك الرسم، لكنها محجوبة عمّن يريدها.

وأقدمُ محاولةٍ لسدّ هذه الفجوة وأشهرُها هي html2canvas منذ 2012، وأسلوبها أن تعيد رسم العنصر بنفسها أمراً أمراً على لوحة (canvas) — وثمنُ ذلك ظلالٌ تختفي، وتخطيطٌ ينزاح، وقرابةُ ثانيتين تمرّ قبل أن تظهر صورةُ عنصرٍ كبير.

وSnapDOM مكتبةٌ مفتوحة المصدر تقلب هذا الترتيب: لا ترسم شيئاً بنفسها، بل تجهّز نسخةً مكتفية من العنصر وتترك الرسم لمحرّك المتصفّح — فتنجز الالتقاط نفسه في عُشر الزمن تقريباً، وتلتقط من خصائص CSS الحديثة ما تعجز سابقتها عن التقاطه.

SnapDOM: التقط عنصر HTML وحوّله إلى صورة دون خادم ودون إعادة رسم

لماذا لا تكفي الحلول الموجودة: من التقاط الشاشة إلى html2canvas

الواجهات القريبة من هذه الحاجة لا تسدّها فعلاً. فواجهة التقاط الشاشة (getDisplayMedia) تصوّر نافذةً أو شاشةً كاملة بعد أن يوافق المستخدم صراحةً على ذلك، وهي مصمّمة لمشاركة الشاشة لا لتصدير مكوّنٍ في صفحة. أمّا المتصفّحات بلا واجهة (headless) مثل تلك التي يشغّلها Puppeteer وPlaywright فتلتقط لقطاتٍ دقيقة تماماً، لكنها تعمل على خادم. ولهذا فالتقاط ما يراه المستخدم الآن في متصفّحه يقتضي إعادة بناء حالة الصفحة على الخادم أولاً، بما يستلزمه ذلك من بنيةٍ تحتية إضافية ومن إرسال محتوى الصفحة خارج جهاز المستخدم.

أمّا html2canvas فتعمل داخل المتصفّح، وأسلوبها أن تقرأ العنصر وأنماطه المحسوبة، ثم ترسمه بنفسها أمراً أمراً على لوحة (canvas): تحسب موضع كلّ صندوق، وتحاكي الحواف المستديرة والتدرّجات والظلال بكود جافاسكربت. فهي عملياً تعيد كتابة جزءٍ من محرّك رسم CSS بلغةٍ أبطأ منه بمراحل. والنتيجة المتوقّعة من هذا الأسلوب أنّ ما لم يُنفَّذ داخل المكتبة لا يظهر في الصورة: خصائص CSS الحديثة، وأنماط التخطيط المعقّدة، وأنماط المزج (blend modes) — كلّها مواضع يشيع فيها اختلاف الصورة عن الشاشة. وما يزال ملفّ التعريف (README) الخاصّ بالمشروع يصفه بأنه في حالةٍ تجريبية جداً ولا ينصح باستعماله في بيئة إنتاج، وآخر إصدارٍ له (1.4.1) صدر في يناير 2022.

الفكرة الأساسية: أعطِ المتصفّح صورةً يعرف كيف يرسمها

في مواصفة SVG عنصرٌ اسمه <foreignObject> وظيفته أن يحتضن محتوىً من لغةٍ أخرى داخل رسم SVG — وأشهر استعمالاته احتضان HTML. وحين يضع المتصفّح HTML داخل <foreignObject>، يرسمه بمحرّك التخطيط والرسم نفسه الذي يرسم به الصفحة العادية: التخطيط المرن (flexbox)، والشبكة (grid)، والظلال، والمرشّحات (filters)، والتحويلات (transforms) — كلّها تعمل كما تعمل في الصفحة، لأنها ليست محاكاةً بل المحرّك الأصلي.

وفي الوقت نفسه، ملفّ SVG صورةٌ من وجهة نظر المتصفّح: يمكن إسناده إلى عنصر <img>، ويمكن رسم ذلك العنصر على لوحة <canvas>، ومن اللوحة يُستخرَج PNG أو JPG.

اجتماع هاتين الحقيقتين هو كلّ الحيلة. فإذا أخذنا العنصر المطلوب ووضعناه داخل <foreignObject> في SVG، صار لدينا شيءٌ هو HTML في جوهره وصورةٌ في تعامل المتصفّح معه. والفرق العملي بين الأسلوبين أنّ html2canvas أشبه برسّامٍ ينظر إلى مستندٍ ويعيد رسمه بيده معتمداً على ما يستطيع تقليده، بينما SnapDOM يضع المستند نفسه في آلة تصوير المتصفّح.

هذا الاختيار ليس ابتكاراً خاصاً بـ SnapDOM؛ فمكتبتا dom-to-image وhtml-to-image تسلكان الطريق نفسه. ما يميّز SnapDOM هو كيف ينفّذ خطوات التجهيز، وهو ما يفسّر فرق السرعة الكبير بينه وبينهما في الحالات الثقيلة.

ما الذي يجري بالضبط عند الالتقاط

الحيلة أعلاه تبدو بسيطة، لكنّ العنصر لا يُنقَل إلى <foreignObject> كما هو. فهو في الصفحة يعتمد على محيطه في كلّ شيء تقريباً: أنماطه تأتي من أوراق أنماط خارجية، وخطوطه تُحمَّل من خوادم، وصوره تُجلَب بروابط. وحين يُقتطَع من الصفحة ويوضع في صورة، ينقطع عن هذا المحيط كلّه. لذلك يمرّ الالتقاط بسلسلة خطوات تجعل النسخة مكتفية بذاتها قبل تحويلها إلى صورة.

الاستنساخ. يبدأ SnapDOM بنسخةٍ عميقة من شجرة العنصر، فلا يمسّ العنصر الأصلي في الصفحة بأيّ تعديل.

تثبيت الأنماط. لكلّ عقدة في النسخة، يقرأ SnapDOM الأنماط المحسوبة (getComputedStyle) — أي القيم النهائية التي استقرّ عليها المتصفّح بعد تطبيق كلّ أوراق الأنماط — ويثبّتها في النسخة نفسها. ولو كُتبت هذه القيم على كلّ عقدة على حدة لتضخّم النصّ الناتج تضخّماً هائلاً، فالعناصر المتشابهة في صفحةٍ حقيقية تتكرّر بالمئات. لذلك يجمع SnapDOM العقد المتطابقة في أنماطها ويعطيها صنفاً مشتركاً واحداً (.c1، .c2) بدل تكرار الإعلانات على كلٍّ منها. ولهذا الاختصار أثرٌ مباشر في الأداء، لأنّ حجم النصّ الناتج يترجم إلى وقت تحويلٍ وتحليلٍ ورسم.

تجسيد ما ليس عقدة أصلاً. بعض ما تراه على الشاشة ليس موجوداً في شجرة DOM، فلا ينتقل مع النسخة. أوضح مثالٍ العناصر الزائفة ::before و::after: هي محتوىً ينشئه CSS عند الرسم، وليست عقداً يمكن استنساخها. ولهذا يقرأ SnapDOM أنماطها ويحوّلها إلى عقدٍ حقيقية داخل النسخة تحمل المحتوى والأنماط نفسها. والمنطق ذاته يُطبَّق على عدّادات CSS (counter()) التي تُحسب قيمها وتُستبدل بها، وعلى اقتطاع النصّ بـ -webkit-line-clamp الذي يُحوَّل إلى نصٍّ مقتطعٍ فعلاً بدل الاعتماد على خاصيةٍ قد لا تُطبَّق في سياق الصورة.

تضمين الموارد. تُجلَب الصور وتتحوّل إلى روابط بيانات (data URLs) مضمَّنة في النصّ، وتُضمَّن قواعد @font-face للخطوط المستعملة فعلاً في العنصر دون غيرها. وفي حالة خطوط الأيقونات (مثل Font Awesome وMaterial Icons) يتعرّف SnapDOM عليها ويعالجها معالجةً خاصّة، لأن الأيقونة فيها محرفٌ لا صورة.

اللفّ والرسم. تُلفّ النسخة الجاهزة في <foreignObject> داخل SVG، ويُحوَّل SVG كلّه إلى رابط بيانات، ويُسنَد إلى عنصر <img>. عند هذه النقطة يتولّى المتصفّح الرسم. وللحصول على PNG أو JPG، تُرسَم الصورة على <canvas> ثم تُستخرَج منها بالصيغة المطلوبة.

لماذا يجب تضمين كلّ شيء: قيدٌ أمنيّ لا اختيار تصميميّ

قد يبدو تضمين كلّ صورة وكلّ خطّ داخل النصّ إسرافاً، والأسهل ظاهرياً أن يبقى رابط الصورة كما هو ويجلبه المتصفّح عند الرسم. لكنّ هذا ممنوعٌ بقاعدةٍ في المتصفّحات نفسها: ملفّ SVG حين يُستعمل كصورة يعمل في سياقٍ مقيّد لا يستطيع فيه جلب أيّ مورد خارجي، ولا يُسمح له إلا بروابط البيانات المضمَّنة فيه.

وسبب المنع يعود إلى الافتراض الذي بُني عليه تعامل الويب مع الصور. فالمواقع تتعامل معها على أنها محتوىً خامل: موقعٌ يقبل صورةً رمزية من مستخدم يفترض أنها لن تُحدِث اتّصالاً بالشبكة ولن يتغيّر شكلها بعد رفعها. ولو سُمح لـ SVG المستعمَل كصورة بجلب موارد خارجية لانكسر هذا الافتراض: تصير الصورة قادرةً على تتبّع من يشاهدها، وعلى تبديل مظهرها لاحقاً بتغيير المورد البعيد.

من هنا فإنّ كلّ خطوات التضمين ليست تحسيناً للأداء بل شرطاً لعمل الفكرة أصلاً: ما لم يُضمَّن، لن يظهر. وهذا القيد نفسه هو ما يولّد أكثر المشاكل العملية شيوعاً مع هذه المكتبات: الموارد عابرة النطاق (cross-origin).

فحين يجلب SnapDOM صورةً ليحوّلها إلى رابط بيانات، فهو يقرأ بياناتها الثنائية بجافاسكربت، والمتصفّح لا يسمح بقراءة بيانات موردٍ من نطاقٍ آخر ما لم يصرّح ذلك النطاق بالسماح عبر ترويسة Access-Control-Allow-Origin. والمتصفّح أكثر تساهلاً في العرض منه في القراءة، ولهذا تظهر مفارقةٌ تربك كثيرين: الصورة أو الخطّ يظهران على الشاشة بلا مشكلة، ثم يغيبان عن الصورة الملتقَطة.

وللمشكلة حلّان بحسب نوع المورد. فأوراق الأنماط الخارجية (خطوط Google وFont Awesome وأمثالها) تحتاج السمة crossorigin="anonymous" على وسم <link> الذي يحمّلها. أمّا الصور والموارد الأخرى فتُعالَج بوسيط (proxy) عبر الخيار useProxy يجلب المورد نيابةً عن المتصفّح ويعيده بالترويسات المناسبة، أو باستضافة المورد على نطاق يصرّح بالقراءة عبر CORS. والموارد المستضافة على نطاق صفحتك نفسه لا تحتاج شيئاً من ذلك.

الأداء: الأرقام وما وراءها

هذه أرقام القياس المنشورة في مستودع المشروع، وهي متوسّط زمن الالتقاط بالمللي ثانية على Chromium (كلّما قلّ كان أفضل):

السيناريو (عنصر معقّد) SnapDOM html2canvas html-to-image
صغير (200×100) 1.6 68.0 14.3
نافذة منبثقة (400×300) 2.9 87.5 34.8
عرض صفحة (1200×800) 17.5 178.0 429.0
تمرير طويل (2000×1500) 54.0 735.2 984.2
كبير جداً (4000×2000) 171.4 1,800.4 2,611.9

والفارق له سببان مختلفان بحسب المكتبة التي نقارن بها.

أمّا التفوّق على html2canvas فمصدره اختلاف الأسلوب نفسه. فرسم صفحةٍ معقّدة بكود جافاسكربت يمرّ على كلّ عقدة ويحسب هندستها ويحاكي تأثيراتها، وهذا عملٌ ثقيل بطبيعته مهما حُسِّن. بينما SnapDOM ينتهي عمله عند تجهيز النصّ، ويقع الرسم كلّه على محرّك المتصفّح المكتوب بلغةٍ منخفضة المستوى والمحسَّن لهذه المهمّة تحديداً.

وأمّا التفوّق على html-to-image فأدقّ، لأنها تستعمل أسلوب <foreignObject> نفسه. الفارق هنا في كفاءة مرحلة التجهيز: html-to-image تكتب الأنماط المحسوبة على كلّ عقدة على حدة في سمة style، فينتفخ النصّ الناتج انتفاخاً يتصاعد مع حجم الشجرة — ويظهر أثر ذلك في الجدول أعلاه: الفارق بينهما في العنصر الصغير نحو 13 مللي ثانية لا يلحظها مستخدم، بينما يبلغ في العنصر الأكبر نحو 2.4 ثانية، وتتّسع النسبة معه من تسعة أضعاف إلى خمسة عشر ضعفاً. وSnapDOM يقلّص هذا الحجم بإزالة تكرار الأنماط في أصناف مشتركة، ويضيف إليه ذاكرات تخزين مؤقّت للأنماط والصور والموارد بين الالتقاطات، وفحصاً مسبقاً يتخطّى المعالجات المكلفة (كمعالجة العناصر الزائفة) حين لا تحتاجها الشجرة.

وهذه الأرقام تُقرأ بتحفّظ. فهي قياسٌ ينشره صاحب المشروع على سيناريوهات اختياره، وأحد طرفَي المقارنة فيه — html2canvas — متوقّف عن التطوير منذ 2022. وقد أفاد مطوّرٌ في نقاشٍ على Hacker News بأنّ SnapDOM كان أبطأ من html2canvas في تطبيقه هو وأقلّ دقّةً في المخرجات — وهو ما يُتوقّع، لأنّ زمن الالتقاط يتبع بنية الشجرة وعدد الموارد فيها لا حجم العنصر وحده. القاعدة العملية أن تقيس على واجهتك أنت قبل أن تبني قراراً على جدول.

الاستخدام العملي

التثبيت من npm أو عبر رابط CDN مباشر:

CODE
npm i @zumer/snapdom
HTML
<script src="https://unpkg.com/@zumer/snapdom/dist/snapdom.js"></script>

أبسط استعمال هو الاختصارات المباشرة، وكلّها تعيد وعداً (Promise):

JavaScript
import { snapdom } from '@zumer/snapdom';

const el = document.querySelector('#card');
const img = await snapdom.toPng(el);
document.body.appendChild(img);

وإلى جانب toPng هناك toJpg وtoWebp وtoSvg وtoCanvas وtoBlob وdownload.

وحين تحتاج أكثر من صيغةٍ من العنصر نفسه، استدعِ snapdom مرّةً واحدة واحتفظ بالنتيجة. فالاستنساخ والتضمين — وهما الجزء المكلف — يقعان مرّةً واحدة، ثم يُصدَّر منهما ما شئت:

JavaScript
const result = await snapdom(el);

const png = await result.toPng();
const blob = await result.toBlob({ type: 'webp' });
await result.download({ format: 'jpg', filename: 'card.jpg' });

في React، الالتقاط يجري من مرجع (ref) وداخل معالج حدثٍ أو بعد التركيب (mount) — لا أثناء التصيير (render)، لأنّ العنصر لا يكون قد وُضع في الصفحة بعد فتخرج الصورة فارغة:

JavaScript
import { useRef } from 'react';
import { snapdom } from '@zumer/snapdom';

export function Card() {
  const ref = useRef(null);

  const capture = async () => {
    const img = await snapdom.toPng(ref.current, { scale: 2 });
    document.body.appendChild(img);
  };

  return (
    <>
      <div ref={ref} className="card">Hello</div>
      <button onClick={capture}>Capture</button>
    </>
  );
}

الخيارات التي تحتاجها فعلاً

الخيارات كثيرة، لكنّ حفنةً منها تغطّي أغلب الاستعمال:

  • scale وdpr: لمضاعفة دقّة المخرَج. الالتقاط بـ scale: 2 يعطي صورةً بضعف الأبعاد، وهو ما تحتاجه لصورةٍ تُطبع أو تُعرض على شاشةٍ عالية الكثافة.
  • embedFonts: تضمين الخطوط المخصّصة. وهو معطّل افتراضياً، ولهذا فأشهر مفاجأةٍ للمستخدم الجديد أن يخرج نصّ الصورة بخطٍّ نظاميّ بدل خطّ الموقع. فعّله متى كان الخطّ جزءاً من الهوية البصرية للعنصر.
  • backgroundColor: لون خلفية يُملأ به الشفاف. وهو مهمّ في JPG تحديداً، فالصيغة لا تدعم الشفافية أصلاً.
  • exclude وfilter: استثناء عناصر من الالتقاط — الأول بمحدّدات CSS، والثاني بدالّة تفحص كلّ عقدة وتردّ false لإسقاطها. تُستعمل لإخفاء أزرار التصدير نفسها أو عناصر الواجهة التي لا معنى لها في الصورة.
  • clip: التقاط منطقةٍ محدّدة فقط بإحداثياتها، أو الجزء الظاهر في الشاشة بتمرير القيمة 'viewport'. والمحتوى الواقع خارج المنطقة يُستبعَد من المعالجة، فيقلّ الزمن على الشجرات الكبيرة.
  • useProxy: الوسيط الذي يحلّ مشكلة الموارد عابرة النطاق كما سبق.

وهناك خيارٌ يستحقّ الانتباه في الحالات الصعبة: reconcile. فالنسخة المستنسخة تُقاس أحياناً بمقاسٍ يختلف قليلاً عن العنصر الحيّ، فيعيد النصّ التفافه (re-wrap) داخل الصورة ويبدو التخطيط منزاحاً. وتفعيل reconcile يجعل SnapDOM يقيس النسخة مقابل العنصر الأصلي ويثبّت أيّ صندوقٍ اختلف مقاسه على مقاسه الحقيقي. وثمنه مضاعفة زمن الالتقاط تقريباً، ولهذا يبقى معطّلاً افتراضياً وتنبّه المكتبة في وحدة تحكّم المتصفّح (console) حين تلاحظ التقاطاً قد يستفيد منه.

نظام الإضافات

يعرض SnapDOM خطّافات (hooks) عند كلّ مرحلة من مراحل الالتقاط: beforeClone وafterClone وbeforeRender وafterRender وbeforeExport وafterExport، مع خطّافين يحيطان بالعملية كلّها هما beforeSnap وafterSnap. وكلّ خطّافٍ يستقبل سياق الالتقاط (context) الذي يحمل العنصر الأصلي والنسخة والخيارات والمخرجات الوسيطة، ويستطيع تعديلها.

والإضافة كائنٌ عاديّ فيه اسمٌ ودوالّ الخطّافات التي تريد استعمالها. المثال التالي يضيف طبقة تعتيمٍ فوق النسخة قبل الرسم:

JavaScript
export function overlayPlugin(options = {}) {
  const color = options.color ?? 'rgba(0,0,0,0.25)';

  return {
    name: 'overlay',
    async afterClone(context) {
      const overlay = document.createElement('div');
      overlay.style.position = 'absolute';
      overlay.style.inset = '0';
      overlay.style.background = color;
      context.clone.appendChild(overlay);
    }
  };
}

وتُسجَّل الإضافة عالمياً بـ snapdom.plugins(...) لتنطبق على كلّ الالتقاطات، أو تُمرَّر في خيار plugins لالتقاطٍ واحد.

الأهمّ في هذا النظام أنه يسمح بإضافة صيغ تصدير جديدة لا يعرفها جوهر المكتبة. وهو ما استُعمل فعلاً في الإضافات الرسمية: تصدير إلى PDF، وإلى ملفّ HTML مكتفٍ بذاته بأنماطه وخطوطه المضمَّنة، وتسجيل الحركة إلى GIF أو إلى فيديو (MP4 أو WebM بحسب المتصفّح) عبر MediaRecorder. ومنها إضافةٌ تخرج بحزمةٍ مهيّأة لنموذجٍ لغوي: لقطة مشروحة مع خريطة العناصر وإحداثياتها ونصّ موجَّه.

القيود التي يجب أن تعرفها قبل أن تبني عليها

الموارد عابرة النطاق هي القيد الأول والأكثر إزعاجاً، وقد سبق شرح سببه وحلّيه. وهو ليس عيباً في المكتبة بل نتيجة مباشرة لنموذج أمان المتصفّح.

بكسلات الفيديو لا تنتقل إلى <foreignObject>. فالتقاط عنصر <video> يحتاج رسم الإطار الحالي على لوحةٍ أولاً ثم التقاط اللوحة، وهو ما تعالجه المكتبة بالتقاط الإطار الحالي أو صورة الملصق (poster) بدل البثّ الحيّ. ويقع مثل هذا في لوحات WebGL، فثمّة خللٌ مفتوح في المشروع تخرج فيه هذه اللوحات شفافةً في مخرَج PNG وSVG منذ الإصدار 2.22.0.

الخطوط المتغيّرة (variable fonts) التي تُضبط بمحاور مثل الوزن والعرض والميل ما تزال ضعيفة الدعم، ويختلف سلوكها بين المتصفّحات — وهذا خللٌ مفتوح في المشروع لا مجرّد نقصٍ في التوثيق.

Safari يعامَل معاملةً خاصّة في أكثر من موضع: صيغة WebP تسقط فيه إلى PNG، والالتقاط مع تضمين الخطوط أو مع صور الخلفية أبطأ فيه بسبب خللٍ معروف في WebKit يتعلّق بتوقيت فكّ ترميز الخطوط. المكتبة تتعامل مع هذا داخلياً بانتظار الخطوط المستعملة فعلاً والتحقّق من أول رسمٍ على اللوحة، فلا يحتاج المطوّر إلى ضبط شيء، لكنّ فارق السرعة يبقى.

والقيد الأهمّ في تقديري هو طبيعة مخرَج SVG. حين تصدّر إلى SVG، فما تحصل عليه ليس رسماً متجهاً (vector) بالمعنى المتعارف عليه، بل HTML ملفوفٌ داخل غلاف SVG. ونتيجة ذلك أنّ الملفّ يبقى محتاجاً إلى محرّك عرض HTML ليُقرأ، فلا يفتحه Inkscape ولا Illustrator عرضاً صحيحاً لأنهما لا يملكان محرّكاً كهذا. فصيغة SVG هنا صيغة نقلٍ بين المتصفّحات لا صيغة تبادلٍ مع أدوات التصميم؛ ومن أراد ملفّاً يفتحه أيّ برنامج فالوجهة هي PNG.

ويبقى قيدٌ عامّ ذكره صاحب المشروع نفسه: foreignObject وسيلةٌ بدائية نسبياً لاحتضان HTML داخل SVG، وتطبيقها يختلف بين المتصفّحات في التفاصيل الدقيقة، فتقع أحياناً فروقٌ صغيرة بين الشاشة والصورة. وسجلّ المشروع يعكس ذلك بوضوح: أكثر من ثلاثمئة مسألة مغلقة، كثيرٌ منها فروق دقّةٍ في حالاتٍ بعينها — جداول، ومعادلات رياضية، وأطر مضمّنة (iframes)، وعناصر بمواضع مطلقة.

أين يقف مقابل البدائل

SnapDOM html2canvas html-to-image Puppeteer/Playwright
مكان التنفيذ المتصفّح المتصفّح المتصفّح خادم
الأسلوب foreignObject إعادة رسم بجافاسكربت foreignObject متصفّح حقيقي
صيغ التصدير PNG، JPG، WebP، SVG، Canvas، Blob، وإضافات PNG، JPG PNG، JPG، SVG كلّ ما يدعمه المتصفّح
الدقّة مع CSS الحديثة تتبع المتصفّح محدودة بما نُفِّذ داخلياً تتبع المتصفّح كاملة
الصيانة نشطة آخر إصدار مستقرّ 1.4.1 (يناير 2022) نشطة نشطة
التنزيلات الأسبوعية على npm ~385 ألفاً ~16.6 مليوناً ~5.8 ملايين

الأرقام الأخيرة تستحقّ التوقّف: html2canvas ما يزال الأكثر تحميلاً بفارقٍ هائل رغم توقّف تطويره، وهو أثر سنواتٍ من التراكم في المشاريع القائمة والأدلّة القديمة، لا حكمٌ على جودته اليوم.

والخلاصة العملية للاختيار: إن كنت تصدّر ما يراه المستخدم في متصفّحه — بطاقة، أو مخطّطاً، أو تقريراً، أو إيصالاً — فالحلّ داخل المتصفّح هو الصحيح، وSnapDOM اليوم أنضج خياراته. وإن كنت تولّد صوراً على الخادم من قوالب تتحكّم أنت بها (صور المشاركة الاجتماعية، وتقارير مجدولة)، فمتصفّحٌ بلا واجهة يعطيك دقّةً كاملة بلا أيّ من هذه القيود، لأنه المتصفّح نفسه بلا وساطة.

هل ينتهي كلّ هذا بواجهةٍ أصلية؟

هذه المكتبات كلّها — SnapDOM وسابقاتها — بُنيت لتعويض قدرةٍ يملكها المتصفّح ولا يعرضها للجافاسكربت. وهذه القدرة بالذات هي ما يقترح HTML-in-Canvas عرضَها: واجهة برمجية تتيح رسم عنصر DOM حيٍّ مباشرةً على لوحة <canvas> عبر دوالّ مثل drawElementImage، دون استنساخٍ ولا تضمينٍ ولا foreignObject.

الاقتراح انتقل في Chrome من تجربةٍ خلف راية إلى تجربة أصل (origin trial) بدأت في الإصدار 148 في مايو 2026 وتمتدّ إلى الإصدار 150. فهو متاحٌ للتجريب على مواقع حقيقية بترخيصٍ مؤقّت، لا مستقرّاً ولا معياراً بعد. ولم يصدر موقفٌ داعم من Firefox ولا من Safari حتى الآن، وهو ما يجعل الاعتماد عليه اليوم سابقاً لأوانه. وقد أضاف SnapDOM إضافةً تجريبية تستعمل هذه الواجهة في المتصفّحات التي تتيحها.

في المحصّلة

المشروع بدأ أداةً داخلية في مشروعٍ آخر لواجهاتٍ قابلة للتكبير، ثم صار مكتبةً مستقلّة قاربت ثمانية آلاف نجمة على GitHub في نحو خمسة عشر شهراً. وهو مرخّص بـ MIT، بلا أيّ تبعية، وحجمه نحو 49 كيلوبايت بعد الضغط، ووتيرة إصداراته سريعة — 55 إصداراً على GitHub حتى الآن، آخرها الإصدار 2.23.2 في مطلع أغسطس 2026.

ولهذه الوتيرة دلالةٌ مزدوجة. فسرعة الإصدارات ليست نشاطاً فحسب، بل هي أيضاً مؤشّر على أنّ ملاحقة الفروق الدقيقة بين ما يرسمه المتصفّح على الشاشة وما يرسمه داخل <foreignObject> عملٌ لا ينتهي — وسجلّ المسائل المغلقة يشهد بذلك. والمكتبة تبلغ دقّةً عالية بالاتّكاء على محرّك المتصفّح بدل أن تنافسه، لكنها تبقى تعمل في ثغرةٍ لم تُصمَّم منصّة الويب لسدّها. فإن استقرّت واجهة HTML-in-Canvas ووجدت دعماً عابراً للمتصفّحات، فقد تصير هذه الطبقة كلّها تفصيلاً تاريخياً — وحتى ذلك الحين، هي أفضل ما يمكن بناؤه فوق ما تتيحه المنصّة اليوم.

ولمن أراد أن يجرّب المكتبة بنفسه أو يقرأ توثيقها كاملاً:

جرّب SnapDOM مباشرةً التوثيق الرسمي المستودع على GitHub
التصنيفات

قد تُعجبك هذه المشاركات

4419914284293787151

العلامات المرجعية

قائمة العلامات المرجعية فارغة ... قم بإضافة مقالاتك الآن

    البحث