الجواب القصير
افتح الصورة إلى Data URI، وأفلِت الصورة فيها، واختر خاصيةَ CSS مخصَّصة، والصق السطر في أعلى ورقة أنماطك. ثم استعمله background-image: var(--logo) حيثما احتجته.
افعل ذلك حين تكون الصورةُ صغيرة — أيقونةٌ أو نقطةُ قائمة أو سهمٌ أو نقشٌ — ومطلوبةً في كل صفحة. ولا تفعله بصورة فوتوغرافية. وكلُّ ما دون ذلك هو لماذا اختلفت الجملتان، وكيف تعرف أيَّهما تنظر إليه.
ما عنوانُ data URI فعلاً
عنوانٌ يحوي الشيء بدل أن يشير إليه. فحيث تقول ورقةُ الأنماط عادةً
background-image: url("logo.png"); فيذهب المتصفح ويجلب logo.png، يقول عنوانُ data URI
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."); وليس ثمّة ما يُجلَب: فالصورةُ هناك أصلاً، مكتوبةً محارفَ. وله ثلاثةُ أجزاء. data: هو المخطَّط. وimage/png هو نوعُ الوسيط، والمتصفحُ يصدّقه تماماً — والمزيدُ عن ذلك أدناه. وكلُّ ما بعد الفاصلة هو الملف.
وتلك هي الفكرةُ كلها. وليست حيلةً ولا التفافاً؛ فهي في المعايير منذ 1998 وتعمل في كل متصفح شُحِن منذ ذلك الحين.
ماذا يشتري لك: رحلةُ ذهاب وإياب أقلّ
التوفيرُ ليس في عرض النطاق. بل في الطلب.
فالمتصفحُ لا يستطيع طلبَ logo.png حتى يقرأ ورقة الأنماط التي تذكره، ولا يستطيع قراءةَ ورقة الأنماط حتى يجلبها. فصورةُ الخلفية العادية على عمق رحلتَي ذهاب وإياب على الأقل في تحميل الصفحة، وعلى هاتف على شبكة بطيئة قد تكون رحلةُ الذهاب والإياب مئتَي جزء من الألف من الثانية أو نحوها مهما كان الملفُّ صغيراً. فالسهمُ بستمئة بايت لا يكلّف نقلُه شيئاً تقريباً ويظلّ قادراً على أن يكلّف ربعَ ثانية ليصل.
ومضمَّناً، يصل مع ورقة الأنماط. وتلك هي المنفعةُ كلها، وهي لأيقونة صغيرة تظهر في أعلى الصفحة منفعةٌ حقيقية.
ماذا يكلّف: الثلثَ، ثم التخزينَ المؤقت
base64 يضيف نحو الثلث. فثلاثةُ بايتات من الملف تصير أربعةَ محارف، لأن ذلك ما يلزم لكتابة بايتات كيفما كانت بمحارف يسمح بها العنوان وحدها. وليس ثمّة مرمِّزٌ ذكيّ يتجنّب ذلك. فملفُّ PNG بـ 9 كيلوبايتات هو 12 كيلوبايتاً من ورقة الأنماط.
والضغطُ لا يعيده. وهذا هو الجزءُ الذي يفترضه الناس. فـ gzip وBrotli يعملان بإيجاد التكرار، وملفاتُ PNG وJPEG وWebP مضغوطةٌ أصلاً — فلم يبقَ فيها تكرارٌ يُذكَر، وbase64 لا يضيف شيئاً منه. وعملياً تسترجع نحو عُشر الثلث، لا الثلثَ كلَّه. (وملفُّ SVG هو الحالةُ المعاكسة، والقسمُ التالي عنه.)
ويكفّ عن كونه ملفاً. وهذه هي الكلفةُ التي لا تظهر في أي قياس يُرجَّح أن تأخذه، وهي التي تهمّ عند الحجم:
- لا يمكن تخزينُه مؤقتاً وحده. فالصورةُ العادية تُجلَب مرةً وتُعاد لسنة. والمضمَّنةُ جزءٌ من ورقة الأنماط، فهي تحيا وتموت بمدخل التخزين المؤقت لورقة الأنماط.
- وتغييرُ أي شيء يعيد تنزيل كل شيء. فأصلِح هامشاً، واشحن ورقةَ أنماط جديدة، فينزّل كلُّ زائر الصورةَ المضمَّنة من جديد معها — صورةً لم تتغيّر منذ سنتين.
- وهو على المسار الحرج. فورقةُ الأنماط تحجب الرسم. والصورةُ لا تحجبه. وتضمينُ صورة ينقلها من الصنف الثاني إلى الأول: فالصفحةُ لا تستطيع الطلاء حتى يصل الشيءُ كلُّه، والصورةُ معه.
- ولا يمكن جلبُه بالتوازي. فالمتصفحاتُ تنزّل أشياء كثيرة في آن. والصورةُ المضمَّنة ليست شيئاً منفصلاً، فلا تنال من ذلك شيئاً.
وعتباتٌ تقريبية، وهي حيث تتغيّر النصيحة لا حيث يفعل المتصفحُ شيئاً مختلفاً: دون نحو 2 كيلوبايت هو مكسبٌ صريح؛ وحتى نحو 10 كيلوبايتات ما زال يستحقّ عادةً لشيء في كل صفحة؛ وبعد 50 كيلوبايتاً هو خطأٌ بلا رسالة خطأ. والأداة تقول لك في أي نطاق تحطّ كلُّ نتيجة، وعددُ المحارف بجانبه.
لا ترمّز ملف SVG بـ base64 أبداً
وهذا أشيعُ خطأ واحد في الصور المضمَّنة، ويقع فيه المصدِّرون وملحقاتُ البناء بقدر ما يقع فيه الناس.
ملفُّ SVG نصّ. والعنوانُ يحمل نصّاً أصلاً. وليس ما يلزم تهريبُه إلا حفنةُ محارف — % و# و< و > وعلامةُ التنصيص التي لففته بها — وكلُّ ما عداها يمكن تركُه كما هو بالضبط. وترميزُه على ذلك الوجه يعطيك عنواناً أقصرَ نموذجياً بنحو الخُمس من base64 للملف نفسه، وهو يُضغَط بعدها كالنصّ لا كالضجيج.
وهو كذلك يظل مقروءاً:
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E..."); فتستطيع رؤية viewBox. وتستطيع تغيير لون الملء في محرِّرك دون فكّ ترميز شيء. ورمّز الملفَّ نفسه بـ base64 فيصير جداراً من الحروف لن يلمسه أحدٌ أبداً بعدها. والصورة إلى Data URI تفعل هذا آلياً لكل ما يتبيّن أنه SVG، وفيها مربّعُ تأشير لسلسلة الأدوات النادرة التي تصرّ على ;base64.
خطأُ التنصيص الذي لا يكسر إلا ملفات SVG
تسمح CSS بكتابة url() بلا علامات تنصيص، وذلك في اسم ملف عادي حسن:
background-image: url(logo.png); وافعل الشيء نفسه بملف SVG مرمَّزٍ بالنسبة المئوية فينكسر. فرمزُ url() غيرُ المنصَّص ينتهي عند أول مسافة أو قوس أو علامة تنصيص أو محرف تحكّم — وملفُّ SVG مملوءٌ بالمسافات، بين كل خاصيتين وكل رقمين في مسار. فيصير التصريحُ عندئذ باطلاً، وCSS تُسقِط التصريحات الباطلة بصمت، فلا تحصل على خلفية ولا على خطأ.
والعلاجُ علاماتُ التنصيص، في كل مرة:
background-image: url("data:image/svg+xml,%3Csvg ... %3E"); وذلك أيضاً سببُ ألا يحتاج المرمِّزُ إلى تهريب المسافات — فهي قانونيةٌ تماماً داخل عنوان منصَّص، وتهريبُ كل واحدة %20 كان سيكلّف ثلاثةَ محارف لكل مسافة في الملف. والقراران يذهبان معاً: نصّص العنوان، وتستطيع ترك المسافات وشأنها. وكلُّ شكل تنتجه الأداةُ منصَّصٌ لهذا السبب بالضبط.
نوعُ الوسيط لا بدّ أن يكون صحيحاً
عنوانُ data URI يعلن نوعه هو، والمتصفحُ يأخذه على كلمته. وليس ثمّة تراجعٌ باستشمام النوع كما في ملف مجلوب: فقل image/png عن شيء هو JPEG حقاً فلا تُرسَم الصورة، وبلا رسالة في أي موضع نافع.
وذلك يهمّ لأن امتدادات الملفات تكذب. فالصورةُ المصدَّرة بصيغة JPEG والمسمّاةُ logo.png شيءٌ عاديٌّ أن تجده على قرص. أما البايتاتُ الأولى القليلة من ملف صورة فتقول ما هو بلا لبس — فلكل صيغة توقيع — فينبغي للأداة أن تقرأ الملف لا اسمه. والتي هنا تفعل، وتقول لك حين يختلف الاثنان.
وصيغتان جديرتان بالمعرفة لأنهما تفشلان على وجه محيّر. HEIC، وهي ما يصوّر به iPhone، وTIFF، وهي ما تنتجه الماسحات، كلتاهما تصنعان عناوين data URI صحيحةً تماماً لا يرسمها أيُّ متصفح إلا Safari. فالعنوانُ ليس مكسوراً؛ بل الصيغةُ ببساطة ليست ممّا يدعمه الويب. حوّل أولاً.
البياناتُ الوصفية التي لم تقصد نشرها
عنوانُ data URI نسخةٌ من الملف، بايتاً ببايت. ولا شيء يُفَكُّ ويُرمَّز من جديد، وذلك هو المقصودُ عادةً — فلا تُفقَد جودة — لكنه يعني كذلك أن كلَّ شيء آخر في الملف يأتي معه.
والصورةُ الخارجةُ من هاتف توّاً تحمل EXIF: إحداثياتِ GPS حيث التُقِطت، والطابعَ الزمني، وطرازَ الكاميرا وكثيراً رقمَها التسلسلي. وقد يكون ذلك 30 كيلوبايتاً من الملف. ومضمَّناً، يصير 40 كيلوبايتاً من base64 في ورقة أنماطك، على المسار الحرج لكل صفحة — وعنوانَ منزل مودَعاً في مستودع، في صورة لن يخطر لأحد أن ينظر فيها أبداً.
فجرّده أولاً بـ عارض EXIF ومُزيله، وهو يعيد كتابة الحاوية دون أن يمسّ الصورة؛ وثمّة دليلٌ لذلك كذلك. والصورةُ إلى Data URI تقرأ كم من البيانات الوصفية في ملف JPEG أو PNG أو WebP وتقوله قبل أن تنسخ شيئاً.
أين تضعه، متى صار عندك
إن ظهرت الصورةُ في قاعدة واحدة، فضع العنوان في تلك القاعدة. وإن ظهرت في أكثر من واحدة — والأيقوناتُ تفعل عادةً، متى عددت حالة التحويم والسمة الداكنة — فأعلنها مرةً خاصيةً مخصَّصة:
:root {
--icon-search: url("data:image/svg+xml,%3Csvg ... %3E");
}
.search-field { background-image: var(--icon-search); }
.search-button::before { content: var(--icon-search); } فعنوانٌ بـ 3 كيلوبايتات ملصوقٌ في أربع قواعد هو 12 كيلوبايتاً من ورقة الأنماط وأربعةُ مواضع تحرّرها حين تتغيّر الأيقونة. والخاصيةُ المخصَّصة واحدةٌ من كلٍّ. وهي كذلك الشكلُ الذي يجعل السمات تعمل: أعد تعريف --icon-search داخل استعلام وسيط فيتبعه كلُّ استعمال له.
ولوسم <img> بدل CSS، ضمّن width و height. فالصورةُ المضمَّنة تُحمَّل فوراً، فالمقاسُ الناقص إزاحةُ تخطيط تحدث أسرع من أن تُرى وتُحسَب عليك مع ذلك. والاستثناءُ SVG: فالتي لا تحمل إلا viewBox ليس لها مقاسٌ بالبكسل خاصٌّ بها، وكتابةُ افتراض المتصفح 300×150 على الوسم تثبّت صورةً قابلةً للقياس عند مقاس لم يخترْه أحد.
واترك alt فارغاً إلا أن يكون عندك شيءٌ صادقٌ تضعه فيه. فأنت وحدك تعرف أتحمل الصورةُ معنىً أم هي زينة، والوصفُ المخمَّنُ من اسم ملف أسوأُ لمن يستعمل قارئة شاشة من ألا يكون ثمّة وصفٌ أصلاً.
حين يكون الجوابُ «لا تفعل»
إن كانت الصورةُ فوق نحو 50 كيلوبايتاً مرمَّزةً، فالتضمينُ هو الأداة الخاطئة ولا يصلحه أيُّ قدر من العناية بالترميز. والبدائلُ، بترتيب ما يستحقّ التجربة:
- صغّرها. فمعظمُ الصور الأكبر من أن تُضمَّن أكبرُ من اللازم عموماً. وضاغط الصور سيوصل صورةً فوتوغرافية إلى حجم تسمّيه، و أداةُ تغيير حجم الصور ستنزّل الأبعاد بالبكسل إلى ما يستعمله التخطيطُ فعلاً — وهو المشكلةُ الحقيقية كثيراً جداً.
- أعد رسمها ملفَّ SVG. فالأيقونةُ المصدَّرة ملفَّ PNG بـ 40 كيلوبايتاً كثيراً ما تكون ملفَّ SVG بتسعمئة بايت. وليس ذلك فرقَ ضغط، بل فرقُ صيغة، وهو يحلّ كذلك مشكلة الشاشات عالية الكثافة.
- اتركها ملفاً وحمّلها مسبقاً.
<link rel="preload" as="image">يبدأ الجلب فوراً دون نقل البايتات إلى المسار الحرج. فينال معظمَ منفعة التضمين ولا شيء من كلفة التخزين المؤقت.
لا شيء من هذا يحتاج رفعاً
ترميزُ ملف بـ base64 حساب. وهو دالّتان عند المتصفح منذ البداية — btoa وencodeURIComponent — وليس ثمّة سببٌ تقنيٌّ البتة لأن تسافر صورةٌ إلى خادم وتعود لتُكتَب على وجه آخر. وأيُّ محوِّل يرفع ملفك ليفعل هذا يرفعه لأسبابه هو لا لأسبابك أنت.
والأداة هنا لا ترسله إلى أي مكان: Content-Security-Policy الخاصةُ بالصفحة تسمّي كل عنوان يحقّ لها الاتصال به، ولا واحدٌ منها يخصّ هذا الموقع. حمّل الصفحة، واقطع الاتصال بالإنترنت، ورمّز شيئاً رغم ذلك إن كنت تفضّل أن تتحقّق على أن يُقال لك. هل رفع الملفات إلى المحوِّلات على الإنترنت آمن؟ يعرض ثلاثة فحوص أخرى تستطيع إجراءها على أي أداة.