تحديد الموقع الجغرافي وواجهات برمجة المتصفح
مقدمة في واجهات برمجة المتصفح
توفر متصفحات الويب الحديثة مجموعة غنية من واجهات البرمجة المدمجة التي تمنح JavaScript الوصول إلى إمكانيات الجهاز وميزات المتصفح ومعلومات النظام. تتيح لك هذه الواجهات تحديد الموقع الجغرافي للمستخدم، والتفاعل مع سجل المتصفح، ونسخ النص إلى الحافظة، ومشاركة المحتوى من خلال مربعات المشاركة الأصلية، واكتشاف ما إذا كانت الصفحة مرئية، وأكثر من ذلك بكثير. فهم هذه الواجهات ضروري لبناء تطبيقات ويب حديثة تبدو أصلية ومتجاوبة.
في هذا الدرس، ستتعلم أهم واجهات برمجة المتصفح التي يجب على كل مطور JavaScript معرفتها. نبدأ بواجهة تحديد الموقع الجغرافي لتحديد موقع المستخدم، ثم نستكشف واجهة Navigator لمعلومات المتصفح، وواجهة History للتوجيه من جانب العميل، وواجهة Clipboard لعمليات النسخ واللصق، وواجهة Web Share للمشاركة الأصلية، وواجهة Fullscreen للتجارب الغامرة، وواجهة Page Visibility لاكتشاف تبديل المستخدم بين علامات التبويب.
واجهة تحديد الموقع الجغرافي
توفر واجهة تحديد الموقع الجغرافي الوصول إلى الموقع الجغرافي للجهاز. تستخدم مصادر متعددة لتحديد الموضع، بما في ذلك GPS وشبكات Wi-Fi وتثليث أبراج الخلايا وتحديد الموقع عبر عنوان IP. يتم الوصول إلى الواجهة من خلال كائن navigator.geolocation وتوفر ثلاث طرق رئيسية: getCurrentPosition() لطلب موقع واحد، وwatchPosition() للتتبع المستمر، وclearWatch() لإيقاف التتبع.
getCurrentPosition()
تطلب طريقة getCurrentPosition() الموضع الحالي للجهاز. تأخذ حتى ثلاث وسائط: دالة استدعاء للنجاح، ودالة استدعاء اختيارية للخطأ، وكائن خيارات اختياري. تستقبل دالة النجاح كائن Position يحتوي على الإحداثيات والطابع الزمني.
مثال: الحصول على الموضع الحالي
// الاستخدام الأساسي
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('خط العرض:', position.coords.latitude);
console.log('خط الطول:', position.coords.longitude);
console.log('الدقة:', position.coords.accuracy, 'متر');
console.log('الطابع الزمني:', new Date(position.timestamp));
},
(error) => {
console.error('خطأ في تحديد الموقع:', error.message);
}
);
// مع الخيارات
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
console.log(`الموقع: ${latitude}, ${longitude}`);
console.log(`دقيق إلى ${accuracy} متر`);
},
(error) => {
handleGeolocationError(error);
},
{
enableHighAccuracy: true, // استخدام GPS إذا كان متاحًا
timeout: 10000, // الانتظار حتى 10 ثوانٍ
maximumAge: 60000 // قبول موضع مخزن مؤقتًا حتى دقيقة واحدة
}
);
كائن Position
يحتوي كائن Position المُمرر إلى دالة النجاح على خاصية coords بمعلومات الموقع التفصيلية وخاصية timestamp. يتضمن كائن coords الخصائص التالية:
- latitude -- خط العرض بالدرجات العشرية (متاح دائمًا).
- longitude -- خط الطول بالدرجات العشرية (متاح دائمًا).
- accuracy -- دقة الموضع بالأمتار (متاح دائمًا).
- altitude -- الارتفاع بالأمتار فوق مستوى سطح البحر (قد يكون null).
- altitudeAccuracy -- دقة الارتفاع بالأمتار (قد يكون null).
- heading -- اتجاه السفر بالدرجات في اتجاه عقارب الساعة من الشمال الحقيقي (قد يكون null).
- speed -- السرعة بالأمتار في الثانية (قد يكون null).
مثال: الوصول إلى جميع خصائص الموضع
navigator.geolocation.getCurrentPosition((position) => {
const coords = position.coords;
// متاح دائمًا
console.log('خط العرض:', coords.latitude);
console.log('خط الطول:', coords.longitude);
console.log('الدقة:', coords.accuracy, 'متر');
// قد يكون null حسب إمكانيات الجهاز
if (coords.altitude !== null) {
console.log('الارتفاع:', coords.altitude, 'متر');
console.log('دقة الارتفاع:', coords.altitudeAccuracy, 'متر');
}
if (coords.heading !== null) {
console.log('الاتجاه:', coords.heading, 'درجة');
}
if (coords.speed !== null) {
console.log('السرعة:', coords.speed, 'م/ث');
console.log('السرعة:', (coords.speed * 3.6).toFixed(1), 'كم/س');
}
console.log('الطابع الزمني:', new Date(position.timestamp).toLocaleString());
});
معالجة الأخطاء
تستقبل دالة الخطأ كائن GeolocationPositionError مع خاصية code وخاصية message. هناك ثلاثة رموز خطأ محتملة يجب التعامل معها:
مثال: معالجة شاملة للأخطاء
function handleGeolocationError(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
// الرمز 1: رفض المستخدم طلب الإذن
console.error('تم رفض الوصول إلى الموقع من قبل المستخدم.');
showMessage('يرجى تفعيل الوصول إلى الموقع في إعدادات المتصفح.');
break;
case error.POSITION_UNAVAILABLE:
// الرمز 2: معلومات الموقع غير متاحة
console.error('معلومات الموقع غير متوفرة.');
showMessage('تعذر تحديد موقعك. يرجى المحاولة مرة أخرى.');
break;
case error.TIMEOUT:
// الرمز 3: انتهت مهلة الطلب
console.error('انتهت مهلة طلب الموقع.');
showMessage('استغرق طلب الموقع وقتًا طويلاً. يرجى المحاولة مرة أخرى.');
break;
default:
console.error('حدث خطأ غير معروف في تحديد الموقع.');
showMessage('حدث خطأ غير متوقع.');
break;
}
}
// التحقق من دعم تحديد الموقع قبل استخدامه
function getLocation() {
if (!('geolocation' in navigator)) {
showMessage('تحديد الموقع الجغرافي غير مدعوم من متصفحك.');
return;
}
navigator.geolocation.getCurrentPosition(
onSuccess,
handleGeolocationError,
{ enableHighAccuracy: true, timeout: 10000 }
);
}
watchPosition() و clearWatch()
تراقب طريقة watchPosition() موضع الجهاز باستمرار وتستدعي دالة النجاح كلما تغير الموضع. تُرجع معرف مراقبة يمكنك تمريره إلى clearWatch() لإيقاف التتبع. هذا ضروري لتطبيقات الملاحة ومتتبعات اللياقة البدنية وأي تطبيق يحتاج لمتابعة حركة المستخدم في الوقت الفعلي.
مثال: تتبع الموضع المستمر
let watchId = null;
const positions = [];
function startTracking() {
if (watchId !== null) {
console.log('التتبع قيد التشغيل بالفعل.');
return;
}
watchId = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude, accuracy, speed } = position.coords;
positions.push({
lat: latitude,
lng: longitude,
accuracy: accuracy,
speed: speed,
time: position.timestamp
});
console.log(`تحديث الموضع #${positions.length}`);
console.log(` الموقع: ${latitude.toFixed(6)}, ${longitude.toFixed(6)}`);
console.log(` الدقة: ${accuracy.toFixed(0)}م`);
if (speed !== null) {
console.log(` السرعة: ${(speed * 3.6).toFixed(1)} كم/س`);
}
// تحديث الواجهة بالموضع الجديد
updateMap(latitude, longitude);
updateDistance();
},
(error) => {
handleGeolocationError(error);
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 0 // الحصول دائمًا على موضع حديث
}
);
console.log('بدأ التتبع. معرف المراقبة:', watchId);
}
function stopTracking() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
console.log('توقف التتبع.');
console.log(`إجمالي المواضع المسجلة: ${positions.length}`);
}
}
// صيغة هافرساين لحساب المسافة بين إحداثيتين
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // نصف قطر الأرض بالكيلومتر
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
watchPosition() دالة الاستدعاء كلما اكتشف الجهاز تغييرًا كبيرًا في الموضع. يعتمد التردد على الجهاز وإعداد الدقة وسرعة الحركة. تعيين maximumAge: 0 يضمن الحصول دائمًا على موضع حديث بدلاً من موضع مخزن مؤقتًا. ضع في اعتبارك أن تتبع GPS المستمر يستنزف البطارية بشكل كبير على الأجهزة المحمولة.نموذج الأذونات
يتبع الوصول إلى تحديد الموقع نموذج أذونات صارم. في المرة الأولى التي يطلب فيها الكود الوصول إلى الموقع، يعرض المتصفح مطالبة بالإذن. يمكن للمستخدم السماح أو الرفض أو تجاهل المطالبة. يمكنك استخدام واجهة الأذونات للتحقق من حالة الإذن الحالية دون تشغيل مطالبة:
مثال: التحقق من إذن تحديد الموقع
async function checkLocationPermission() {
try {
const result = await navigator.permissions.query({ name: 'geolocation' });
console.log('حالة الإذن:', result.state);
switch (result.state) {
case 'granted':
// تم منح الإذن بالفعل، المتابعة مباشرة
navigator.geolocation.getCurrentPosition(onSuccess);
break;
case 'prompt':
// لم يُطلب الإذن بعد، عرض شرح أولاً
showExplanation('نحتاج موقعك لعرض النتائج القريبة.');
break;
case 'denied':
// تم رفض الإذن، عرض تعليمات التفعيل
showMessage('الوصول إلى الموقع محظور. يرجى تحديث إعدادات المتصفح.');
break;
}
// الاستماع لتغييرات الإذن
result.addEventListener('change', () => {
console.log('تغير الإذن إلى:', result.state);
});
} catch (error) {
console.error('واجهة الأذونات غير مدعومة:', error);
}
}
واجهة Navigator
يوفر كائن navigator معلومات حول المتصفح والجهاز. يكشف عن خصائص وطرق لاكتشاف إمكانيات المتصفح وتفضيلات اللغة وحالة الشبكة والمزيد. بينما بعض الخصائص متاحة لأسباب تاريخية، فإن العديد من الإضافات الحديثة مفيدة للغاية لبناء تطبيقات ويب متجاوبة.
مثال: خصائص Navigator المفيدة
// سلسلة وكيل المستخدم (استخدم بحذر للاكتشاف)
console.log('وكيل المستخدم:', navigator.userAgent);
// اللغة المفضلة
console.log('اللغة:', navigator.language);
console.log('جميع اللغات:', navigator.languages);
// حالة الاتصال
console.log('متصل:', navigator.onLine);
// عدد أنوية المعالج
console.log('أنوية المعالج:', navigator.hardwareConcurrency);
// ذاكرة الجهاز (تقريبية، بالجيجابايت)
console.log('ذاكرة الجهاز:', navigator.deviceMemory, 'جيجابايت');
// الاستماع لأحداث الاتصال/قطع الاتصال
window.addEventListener('online', () => {
console.log('عاد الاتصال!');
showNotification('تمت استعادة الاتصال.');
});
window.addEventListener('offline', () => {
console.log('انقطع الاتصال.');
showNotification('أنت غير متصل. ستتم مزامنة التغييرات عند إعادة الاتصال.');
});
navigator.userAgent لاكتشاف الميزات. سلاسل وكيل المستخدم غير موثوقة ويمكن تزييفها. بدلاً من ذلك، استخدم اكتشاف الميزات بالتحقق مما إذا كانت واجهات أو خصائص محددة موجودة. على سبيل المثال، استخدم 'geolocation' in navigator بدلاً من تحليل سلسلة وكيل المستخدم.واجهة الشاشة
يوفر كائن screen معلومات حول شاشة المستخدم. يتضمن أبعاد الشاشة والمساحة المتاحة وعمق الألوان وكثافة البكسل. هذه المعلومات مفيدة لتكييف المحتوى مع إمكانيات العرض المختلفة.
مثال: معلومات الشاشة
// أبعاد الشاشة الكاملة
console.log('عرض الشاشة:', screen.width);
console.log('ارتفاع الشاشة:', screen.height);
// المساحة المتاحة (بدون شريط المهام وغيره)
console.log('العرض المتاح:', screen.availWidth);
console.log('الارتفاع المتاح:', screen.availHeight);
// نسبة بكسل الجهاز (للشاشات عالية الكثافة)
console.log('نسبة البكسل:', window.devicePixelRatio);
// اتجاه الشاشة
console.log('الاتجاه:', screen.orientation.type);
console.log('الزاوية:', screen.orientation.angle);
// الاستماع لتغييرات الاتجاه
screen.orientation.addEventListener('change', () => {
console.log('الاتجاه الجديد:', screen.orientation.type);
if (screen.orientation.type.startsWith('landscape')) {
enableLandscapeMode();
} else {
enablePortraitMode();
}
});
واجهة History
تسمح واجهة History لـ JavaScript بالتلاعب بسجل جلسة المتصفح. وهي أساس التوجيه من جانب العميل في تطبيقات الصفحة الواحدة (SPAs). بدلاً من تحميل صفحات جديدة من الخادم، تستخدم تطبيقات SPA واجهة History لتحديث عنوان URL وإدارة حالة التنقل مع الحفاظ على تشغيل التطبيق في نفس الصفحة. الطريقتان الرئيسيتان هما pushState() و replaceState()، والحدث الرئيسي هو popstate.
مثال: استخدام pushState و replaceState
// pushState يضيف إدخالاً جديدًا في السجل
// الصيغة: history.pushState(state, title, url)
// التنقل إلى "صفحة" جديدة بدون إعادة تحميل
history.pushState(
{ page: 'about', section: 'team' }, // كائن الحالة
'', // العنوان (يتم تجاهله)
'/about/team' // عنوان URL الجديد
);
// شريط العنوان يُظهر الآن "/about/team" لكن لم يحدث تحميل صفحة
console.log('عنوان URL الحالي:', window.location.pathname);
// replaceState يعدل الإدخال الحالي
history.replaceState(
{ page: 'about', section: 'team', scrollY: 500 },
'',
'/about/team'
);
// الوصول إلى الحالة الحالية
console.log('الحالة الحالية:', history.state);
// التنقل للخلف والأمام برمجيًا
history.back(); // مثل النقر على زر الرجوع
history.forward(); // مثل النقر على زر التقدم
history.go(-2); // الرجوع إدخالين
history.go(1); // التقدم إدخالاً واحدًا
حدث popstate
ينطلق حدث popstate عندما يتنقل المستخدم عبر السجل (بالنقر على أزرار الرجوع أو التقدم، أو باستدعاء history.back() أو history.forward() أو history.go()). يحتوي كائن الحدث على الحالة التي تم تمريرها إلى pushState() أو replaceState().
مثال: موجه بسيط من جانب العميل
class Router {
constructor() {
this.routes = {};
window.addEventListener('popstate', (e) => {
this.handleRoute(window.location.pathname, e.state);
});
}
addRoute(path, handler) {
this.routes[path] = handler;
}
navigate(path, state = {}) {
history.pushState(state, '', path);
this.handleRoute(path, state);
}
handleRoute(path, state) {
const handler = this.routes[path];
if (handler) {
handler(state);
} else {
this.handle404();
}
}
handle404() {
document.getElementById('app').innerHTML = '<h1>الصفحة غير موجودة</h1>';
}
}
// الاستخدام
const router = new Router();
router.addRoute('/', () => {
document.getElementById('app').innerHTML = '<h1>الصفحة الرئيسية</h1>';
});
router.addRoute('/about', () => {
document.getElementById('app').innerHTML = '<h1>صفحة عنا</h1>';
});
// اعتراض نقرات الروابط للتنقل في SPA
document.addEventListener('click', (e) => {
const link = e.target.closest('a[data-route]');
if (link) {
e.preventDefault();
router.navigate(link.getAttribute('href'));
}
});
pushState()، يتم تسلسل كائن الحالة باستخدام خوارزمية الاستنساخ المهيكل. هذا يعني أنه يمكنك تخزين الكائنات والمصفوفات والتواريخ وبيانات مهيكلة أخرى، لكن لا يمكنك تخزين الدوال أو عناصر DOM أو نسخ الفئات. حافظ على كائن الحالة صغيرًا وقابلاً للتسلسل.واجهة URL
توفر واجهة URL طريقة نظيفة وكائنية التوجه لتحليل وبناء ومعالجة عناوين URL. يُنشئ مُنشئ URL كائنات URL من السلاسل النصية، وتتعامل واجهة URLSearchParams مع معلمات سلسلة الاستعلام.
مثال: العمل مع عناوين URL
// إنشاء وتحليل عناوين URL
const url = new URL('https://example.com:8080/products?category=shoes&sort=price#reviews');
console.log('البروتوكول:', url.protocol); // "https:"
console.log('المضيف:', url.host); // "example.com:8080"
console.log('اسم المضيف:', url.hostname); // "example.com"
console.log('المنفذ:', url.port); // "8080"
console.log('المسار:', url.pathname); // "/products"
console.log('البحث:', url.search); // "?category=shoes&sort=price"
console.log('التجزئة:', url.hash); // "#reviews"
// العمل مع معلمات البحث
const params = url.searchParams;
console.log('الفئة:', params.get('category')); // "shoes"
console.log('الترتيب:', params.get('sort')); // "price"
console.log('هل يوجد لون:', params.has('color')); // false
// تعديل المعلمات
params.set('page', '2');
params.append('color', 'red');
params.delete('sort');
console.log('URL المحدث:', url.toString());
// التكرار على المعلمات
for (const [key, value] of params) {
console.log(`${key} = ${value}`);
}
واجهة الحافظة
توفر واجهة الحافظة طرقًا غير متزامنة للقراءة من حافظة النظام والكتابة إليها. تستبدل نهج document.execCommand('copy') القديم بواجهة حديثة قائمة على الوعود. الطريقتان الأساسيتان هما navigator.clipboard.writeText() لنسخ النص وnavigator.clipboard.readText() للصق النص.
مثال: النسخ واللصق مع واجهة الحافظة
// نسخ النص إلى الحافظة
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('تم نسخ النص إلى الحافظة!');
showNotification('تم النسخ!');
} catch (error) {
console.error('فشل النسخ:', error);
// بديل للمتصفحات القديمة
fallbackCopy(text);
}
}
// قراءة النص من الحافظة
async function pasteFromClipboard() {
try {
const text = await navigator.clipboard.readText();
console.log('النص الملصق:', text);
return text;
} catch (error) {
console.error('فشل قراءة الحافظة:', error);
return null;
}
}
// طريقة نسخ بديلة للمتصفحات القديمة
function fallbackCopy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showNotification('تم النسخ!');
} catch (err) {
console.error('فشل النسخ البديل:', err);
}
document.body.removeChild(textarea);
}
// عملي: زر نسخ لكتل الكود
document.querySelectorAll('.code-block').forEach(block => {
const copyBtn = document.createElement('button');
copyBtn.textContent = 'نسخ';
copyBtn.className = 'copy-button';
copyBtn.addEventListener('click', async () => {
const code = block.querySelector('code').textContent;
await copyToClipboard(code);
copyBtn.textContent = 'تم النسخ!';
setTimeout(() => {
copyBtn.textContent = 'نسخ';
}, 2000);
});
block.style.position = 'relative';
block.appendChild(copyBtn);
});
readText) تتطلب إذنًا صريحًا من المستخدم من خلال مطالبة المتصفح. الكتابة في الحافظة (writeText) مسموح بها استجابة لإيماءات المستخدم (مثل أحداث النقر) بدون مطالبة في معظم المتصفحات. لا تقرأ الحافظة أبدًا بدون إجراء واضح بدأه المستخدم.واجهة Web Share
تستدعي واجهة Web Share آلية المشاركة الأصلية لنظام التشغيل، مما يسمح للمستخدمين بمشاركة المحتوى من خلال التطبيقات المثبتة مثل الرسائل والبريد الإلكتروني ووسائل التواصل الاجتماعي والمزيد.
مثال: استخدام واجهة Web Share
// التحقق من دعم Web Share
function isShareSupported() {
return 'share' in navigator;
}
// مشاركة نص أساسية
async function shareContent() {
if (!isShareSupported()) {
// بديل: عرض مربع مشاركة مخصص أو نسخ الرابط
showCustomShareDialog();
return;
}
try {
await navigator.share({
title: 'اطلع على هذا المقال',
text: 'وجدت هذا المقال المثير للاهتمام حول JavaScript.',
url: window.location.href
});
console.log('تمت المشاركة بنجاح!');
} catch (error) {
if (error.name === 'AbortError') {
console.log('ألغى المستخدم المشاركة.');
} else {
console.error('خطأ في المشاركة:', error);
}
}
}
// مشاركة الملفات (الصور والمستندات)
async function shareFile(file) {
if (!navigator.canShare || !navigator.canShare({ files: [file] })) {
console.log('مشاركة الملفات غير مدعومة.');
return;
}
try {
await navigator.share({
title: 'ملف مشترك',
files: [file]
});
console.log('تمت مشاركة الملف بنجاح!');
} catch (error) {
console.error('خطأ في مشاركة الملف:', error);
}
}
'share' in navigator ووفّر بديلاً للمتصفحات غير المدعومة. البدائل الشائعة تشمل نسخ عنوان URL إلى الحافظة، أو عرض مربع مشاركة مخصص مع روابط لمنصات التواصل الاجتماعي.واجهة ملء الشاشة
تسمح لك واجهة ملء الشاشة بعرض أي عنصر في وضع ملء الشاشة، مستحوذًا على العرض بالكامل. يُستخدم هذا عادة لمشغلات الفيديو ومعارض الصور والعروض التقديمية والألعاب وتجارب القراءة الغامرة.
مثال: العمل مع واجهة ملء الشاشة
// الدخول إلى ملء الشاشة
async function enterFullscreen(element) {
try {
if (element.requestFullscreen) {
await element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
await element.webkitRequestFullscreen(); // Safari
}
console.log('تم الدخول إلى وضع ملء الشاشة.');
} catch (error) {
console.error('فشل الدخول إلى ملء الشاشة:', error);
}
}
// الخروج من ملء الشاشة
async function exitFullscreen() {
try {
if (document.exitFullscreen) {
await document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
await document.webkitExitFullscreen(); // Safari
}
} catch (error) {
console.error('فشل الخروج من ملء الشاشة:', error);
}
}
// تبديل ملء الشاشة
async function toggleFullscreen(element) {
if (document.fullscreenElement) {
await exitFullscreen();
} else {
await enterFullscreen(element);
}
}
// التحقق من حالة ملء الشاشة
function isFullscreen() {
return !!document.fullscreenElement;
}
// الاستماع لتغييرات ملء الشاشة
document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
console.log('العنصر في ملء الشاشة:', document.fullscreenElement.id);
} else {
console.log('تم الخروج من ملء الشاشة.');
}
});
واجهة رؤية الصفحة
تتيح لك واجهة رؤية الصفحة اكتشاف متى تصبح الصفحة مرئية أو مخفية. تعتبر الصفحة مخفية عندما يتحول المستخدم إلى علامة تبويب أخرى أو يصغر نافذة المتصفح أو يقفل الشاشة. هذه الواجهة لا تُقدر بثمن لتحسين الأداء وتجربة المستخدم -- يمكنك إيقاف الرسوم المتحركة مؤقتًا وإيقاف الاستطلاع وكتم الصوت وتوفير الموارد عندما لا يشاهد المستخدم صفحتك بنشاط.
مثال: استخدام واجهة رؤية الصفحة
// التحقق من حالة الرؤية الحالية
console.log('حالة الرؤية:', document.visibilityState);
// القيم: "visible"، "hidden"
console.log('هل هي مخفية:', document.hidden);
// الاستماع لتغييرات الرؤية
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('الصفحة مخفية الآن.');
onPageHidden();
} else {
console.log('الصفحة مرئية الآن.');
onPageVisible();
}
});
// عملي: إيقاف واستئناف العمليات
let animationId = null;
let pollingInterval = null;
function onPageVisible() {
// استئناف الرسوم المتحركة
if (!animationId) {
animationId = requestAnimationFrame(animate);
}
// استئناف استطلاع البيانات
if (!pollingInterval) {
pollingInterval = setInterval(fetchUpdates, 30000);
fetchUpdates(); // جلب فورًا عند العودة
}
// استئناف تشغيل الفيديو
const video = document.querySelector('video');
if (video && video.dataset.wasPlaying === 'true') {
video.play();
}
}
function onPageHidden() {
// إيقاف الرسوم المتحركة مؤقتًا
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
// إيقاف استطلاع البيانات
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
}
// إيقاف تشغيل الفيديو مؤقتًا
const video = document.querySelector('video');
if (video && !video.paused) {
video.dataset.wasPlaying = 'true';
video.pause();
}
// حفظ المسودة أو التقدم
saveDraft();
}
// عملي: تتبع الوقت الفعلي المستغرق في الصفحة
class TimeTracker {
constructor() {
this.totalTime = 0;
this.startTime = null;
this.isTracking = false;
if (!document.hidden) {
this.start();
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.pause();
} else {
this.start();
}
});
window.addEventListener('beforeunload', () => {
this.pause();
this.save();
});
}
start() {
if (!this.isTracking) {
this.startTime = Date.now();
this.isTracking = true;
}
}
pause() {
if (this.isTracking && this.startTime) {
this.totalTime += Date.now() - this.startTime;
this.startTime = null;
this.isTracking = false;
}
}
getTimeSpent() {
let time = this.totalTime;
if (this.isTracking && this.startTime) {
time += Date.now() - this.startTime;
}
return Math.round(time / 1000); // إرجاع الثواني
}
save() {
const seconds = this.getTimeSpent();
console.log(`الوقت في الصفحة: ${seconds} ثانية`);
}
}
const tracker = new TimeTracker();
window.onblur و window.onfocus لاكتشاف متى يشاهد المستخدم صفحتك فعليًا. أحداث blur و focus تنطلق عند التبديل بين النوافذ أو التفاعل مع واجهة المتصفح، مما قد لا يعكس بدقة ما إذا كان محتوى الصفحة مرئيًا. حدث visibilitychange يتتبع تحديدًا ما إذا كانت علامة تبويب الصفحة في المقدمة.تطبيق واقعي: الجمع بين واجهات متعددة
في التطبيقات الحقيقية، غالبًا ما تجمع بين واجهات برمجة متصفح متعددة لإنشاء ميزات متكاملة. إليك مثال يجمع بين واجهة تحديد الموقع وواجهة الحافظة وواجهة Web Share وواجهة رؤية الصفحة في ميزة مشاركة الموقع:
مثال: تطبيق مشاركة الموقع
class LocationSharer {
constructor() {
this.watchId = null;
this.lastPosition = null;
this.isSharing = false;
// إيقاف التتبع مؤقتًا عند إخفاء الصفحة
document.addEventListener('visibilitychange', () => {
if (document.hidden && this.isSharing) {
this.pauseTracking();
} else if (!document.hidden && this.isSharing) {
this.resumeTracking();
}
});
}
async startSharing() {
if (!('geolocation' in navigator)) {
alert('تحديد الموقع الجغرافي غير مدعوم.');
return;
}
this.isSharing = true;
this.resumeTracking();
}
resumeTracking() {
if (this.watchId !== null) return;
this.watchId = navigator.geolocation.watchPosition(
(position) => {
this.lastPosition = position;
this.updateUI(position);
},
(error) => {
console.error('خطأ في الموقع:', error.message);
},
{ enableHighAccuracy: true, maximumAge: 5000 }
);
}
pauseTracking() {
if (this.watchId !== null) {
navigator.geolocation.clearWatch(this.watchId);
this.watchId = null;
}
}
getShareableLink() {
if (!this.lastPosition) return null;
const { latitude, longitude } = this.lastPosition.coords;
return `https://maps.google.com/?q=${latitude},${longitude}`;
}
async shareLocation() {
const link = this.getShareableLink();
if (!link) {
alert('لا يوجد موقع متاح بعد.');
return;
}
if ('share' in navigator) {
try {
await navigator.share({
title: 'موقعي',
text: 'إليك موقعي الحالي.',
url: link
});
} catch (error) {
if (error.name !== 'AbortError') {
console.error('فشلت المشاركة:', error);
}
}
} else {
await navigator.clipboard.writeText(link);
}
}
updateUI(position) {
const { latitude, longitude, accuracy } = position.coords;
const display = document.getElementById('location-display');
if (display) {
display.textContent = `${latitude.toFixed(6)}, ${longitude.toFixed(6)} (ضمن ${accuracy.toFixed(0)}م)`;
}
}
}
const sharer = new LocationSharer();
اكتشاف ميزات واجهات المتصفح
نظرًا لأن ليس كل المتصفحات تدعم كل واجهة، فإن اكتشاف الميزات ضروري. تحقق دائمًا من توفر الواجهة قبل استخدامها، ووفّر بدائل ذات معنى للمتصفحات غير المدعومة:
مثال: أداة اكتشاف الميزات
const BrowserFeatures = {
geolocation: 'geolocation' in navigator,
clipboard: 'clipboard' in navigator,
share: 'share' in navigator,
fullscreen: 'fullscreenEnabled' in document,
visibility: 'visibilityState' in document,
permissions: 'permissions' in navigator,
onLine: 'onLine' in navigator,
orientation: 'orientation' in screen,
// تقرير جميع الميزات المدعومة
report() {
console.group('دعم ميزات المتصفح');
for (const [feature, supported] of Object.entries(this)) {
if (typeof supported === 'boolean') {
console.log(`${feature}: ${supported ? 'مدعوم' : 'غير مدعوم'}`);
}
}
console.groupEnd();
},
// التحقق من ميزة وتشغيل دالة أو بديل
use(feature, callback, fallback) {
if (this[feature]) {
return callback();
} else if (fallback) {
return fallback();
} else {
console.warn(`الميزة "${feature}" غير مدعومة في هذا المتصفح.`);
}
}
};
// الاستخدام
BrowserFeatures.report();
BrowserFeatures.use(
'share',
() => navigator.share({ title: 'مرحبا', url: location.href }),
() => navigator.clipboard.writeText(location.href)
);
أفضل الممارسات لواجهات برمجة المتصفح
يتطلب العمل مع واجهات برمجة المتصفح الانتباه للأمان والأداء وتجربة المستخدم. إليك أفضل الممارسات الرئيسية:
- تحقق دائمًا من الدعم قبل استخدام أي واجهة. استخدم أنماط
'featureName' in navigatorأوtypeof API !== 'undefined'. - اطلب الأذونات في الوقت المناسب. لا تطلب إذن تحديد الموقع عند تحميل الصفحة. انتظر حتى يتخذ المستخدم إجراءً يتطلبه، واشرح لماذا تحتاج الإذن أولاً.
- تعامل مع الأخطاء بلطف. كل استدعاء واجهة يمكن أن يفشل. مشاكل الشبكة ورفض الأذونات والميزات غير المدعومة يجب أن تنتج رسائل مفيدة، وليس واجهات معطلة.
- وفّر بدائل. لكل ميزة واجهة، احصل على بديل للمتصفحات التي لا تدعمها. واجهة Web Share تستخدم نسخ الحافظة كبديل؛ واجهة ملء الشاشة تستخدم نافذة منبثقة كبيرة كبديل.
- احترم خصوصية المستخدم. بيانات تحديد الموقع حساسة. لا تخزن أو تنقل بيانات الموقع أبدًا بدون موافقة المستخدم. وفّر دائمًا طريقة لإيقاف التتبع.
- حسّن لعمر البطارية. تتبع GPS المستمر والاستطلاع المتكرر وتشغيل الرسوم المتحركة على الصفحات المخفية كلها تستنزف البطارية. استخدم واجهة رؤية الصفحة لإيقاف العمليات المكلفة مؤقتًا عندما لا يشاهد المستخدم صفحتك.
- استخدم HTTPS. تتطلب العديد من الواجهات الحديثة سياقًا آمنًا. قدّم تطبيقك دائمًا عبر HTTPS في الإنتاج.
- حافظ على كائنات الحالة صغيرة. عند استخدام واجهة History، خزّن فقط الحد الأدنى من البيانات المطلوبة في كائنات الحالة. كائنات الحالة الكبيرة تستهلك الذاكرة وقد تفشل في التسلسل.
تمرين عملي
ابنِ تطبيق ويب "دفتر المواقع" يجمع بين واجهات برمجة متصفح متعددة. يجب أن يحتوي التطبيق على زر "تسجيل الموقع" يستخدم واجهة تحديد الموقع الجغرافي لالتقاط موضع المستخدم الحالي. يجب أن يعرض كل موقع مسجل خط العرض وخط الطول والدقة والطابع الزمني في قائمة على الصفحة. أضف زر "نسخ جميع المواقع" يستخدم واجهة الحافظة لنسخ جميع المواقع المسجلة كنص منسق. أضف زر "مشاركة الدفتر" يستخدم واجهة Web Share (مع بديل الحافظة) لمشاركة ملخص المواقع المسجلة. نفّذ زر وضع ملء الشاشة لعرض الخريطة باستخدام واجهة ملء الشاشة. استخدم واجهة رؤية الصفحة لإيقاف تتبع GPS مؤقتًا عندما يبدل المستخدم علامات التبويب واستئنافه عند عودته. أضف مؤشر حالة الاتصال باستخدام navigator.onLine وأحداث الاتصال/قطع الاتصال. استخدم واجهة History لإنشاء عرضين (عرض القائمة وعرض الخريطة) مع تحديثات URL مناسبة حتى يعمل زر الرجوع في المتصفح بين العرضين. استخدم واجهة URL لتحليل أي معلمات استعلام موقع عند تحميل الصفحة. اختبر كل ميزة بما في ذلك حالات الخطأ: ارفض إذن تحديد الموقع، واقطع الاتصال، واستخدم متصفحًا غير مدعوم لواجهة Web Share، وتحقق من أن جميع البدائل تعمل بشكل صحيح.