نضع خطة تضاريس تفاعلية في 15 دقيقة


في Toaster ، غالبًا ما يسألون عن كيفية عمل رسم تخطيطي تفاعلي للمنزل ، وخطة لهيكلها الداخلي ، والقدرة على اختيار الطوابق أو الشقق مع معلومات عنهم ، وعرض معلومات حول تفاصيل منتج معين عند تحريك الماوس فوقهم في صورة ، إلخ. لا يتعلق الأمر بنموذج ثلاثي الأبعاد ، بل يتعلق بصورة لها القدرة على إبراز بعض التفاصيل. كل هذه المهام متشابهة وتم حلها بكل بساطة ، ولكن مع ذلك تستمر الأسئلة في الظهور ، لذلك سننظر اليوم في كيفية القيام بهذه الأشياء باستخدام SVG ، محرر رسومي ووصلة جافا سكريبت.


يرجع اختيار SVG إلى أن هذا هو أبسط خيار للتطوير والتصحيح. قابلت أشخاصًا نصحوا بكل ذلك على قماش ، لكن من الصعب للغاية فهم ما يحدث ، وتحتاج إحداثيات جميع النقاط الموجودة على المنحنيات إلى حساب مقدمًا بطريقة أو بأخرى ، لكنني فتحت هنا أدوات المطور وأرى الهيكل بالكامل وجميع الكائنات على الفور ، الذي يوجد به التفاعل ، ويمكن النقر فوق كل شيء آخر باستخدام الماوس في واجهة صديقة للإنسان. بالكاد يختلف الأداء بين اللوحة القماشية المعتادة ثنائية الأبعاد و SVG. قد تمنح WebGL بعض المكافآت في هذا الصدد ، لكن الجدول الزمني للتطوير سيزداد بشكل ملحوظ ، ناهيك عن المزيد من الدعم ، والذي لا يتلاءم دائمًا مع الميزانية. وأود أن أقول "لا يناسب أبدا".


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


الابتداء


سأعرض كل شيء باستخدام Inkscape كمثال ، ولكن يمكن تنفيذ جميع الإجراءات نفسها في أي محرر آخر باستخدام وظائف مماثلة.


للعمل ، نحتاج إلى مربعي حوار رئيسيين:


  • محرر XML (Ctrl + Shift + X أو رمز مع أقواس زاوية) - لعرض بنية المستند في شكل علامات وتحرير العناصر الفردية.
  • التعبئة والسكتة الدماغية (Ctrl + Shift + F أو الرمز مع فرشاة في الإطار) - خاصة لملء الحدود الخارجية.

إطلاقها على الفور والمضي قدما في إنشاء المستند.


إذا قمت بسحبها بطريق الخطأ إلى نافذة منفصلة ، فيمكنك النقر أسفل الإطار العلوي لهذه النافذة (حيث لا يوجد شيء) ، وسحبها مرة أخرى إلى الإطار الرئيسي. هذه ليست بديهية تماما ، لكنها مريحة إلى حد ما.

افتح صورة مع منظر للمنطقة. يمكننا اختيار إدراج الصورة نفسها كسلسلة base64 أو رابط خارجي. لأنه كبير ، حدد الرابط. بعد ذلك سنغير المسار إلى الصورة بأيدينا عند إدخال كل شيء في صفحات الموقع. يتم إنشاء مستند SVG يتم فيه دمج الصورة عبر علامة الصورة.


بالنسبة للصور النقطية المضمنة في SVG ، المضمنة في HTML ، يمكنك استخدام التحميل الكسول ، وكذلك الصور العادية على الصفحات. في هذا المثال ، لن نتطرق إلى مثل هذه التحسينات ، ولكن لا ننساهم في العمل العملي.

في المرحلة الحالية ، نرى أمامنا شيئًا مثل هذا:



الآن قم بإنشاء طبقة جديدة (Ctrl + Shift + N أو قائمة> طبقة> إضافة طبقة). في محرر XML ، نرى أن العنصر g العادي قد ظهر. على الرغم من أننا لم نذهب بعيدًا ، إلا أنه يمكننا تقديم فصل ، سنستخدمه لاحقًا في البرامج النصية.


لا تعتمد على الهوية. كلما كانت الواجهة أكثر تعقيدًا ، أصبح من الأسهل تكرارها والحصول على أخطاء غريبة. وفي مهمتنا ، لا يزال لديهم أي فائدة. لذلك الطبقات أو سمات البيانات هي خيارنا.

إذا نظرت عن كثب إلى بنية المستند في محرر XML ، ستلاحظ وجود الكثير من الأشياء غير الضرورية. سيضيف أي محرر رسومات متجه أكثر أو أقل تعقيدًا شيئًا خاصًا به إلى المستندات. لإزالة كل هذا بيديك مهمة طويلة وعديمة الشكر ، سيضيف المحرر شيئًا جديدًا مرة أخرى. حتى يتم تنظيف SVG من القمامة فقط في نهاية العمل. ويفضل أن يكون ذلك في شكل تلقائي ، نظرًا لوجود خيارات جاهزة ، نفس svgo على سبيل المثال.


ابحث عن أداة تسمى منحنيات رسم Bezier وخطوط مستقيمة (إزاحة + F6). مع ذلك ، سوف نرسم معالم مغلقة حول الكائنات. في مهمتنا ، نحن بحاجة إلى تحديد جميع المباني. على سبيل المثال ، نقصر أنفسنا على ستة ، ولكن في الظروف الحقيقية ، سيكون من المفيد تخصيص وقت مسبقًا لتخطيط جميع الكائنات الضرورية بدقة. على الرغم من أن هناك غالبًا ما يكون هناك العديد من الكيانات المتشابهة ، إلا أن نفس الطوابق في المبنى يمكن أن تكون متطابقة تمامًا. في مثل هذه الحالات ، يمكنك تسريع بعض المنحنيات ولصقها.


بعد أن قمنا بدائرة المباني اللازمة ، نعود إلى محرر XML ، أو نضيف فئات ، أو على الأرجح ، ستكون أكثر ملاءمة ، سمات البيانات مع فهارس لها (من الممكن مع العناوين ، ولكن بما أن لدينا منطقة وهمية ، لا يوجد سوى الفهارس) ، ونقل كل شيء إلى الطبقة التي تم إنشاؤها مسبقا بحيث يتم وضع كل شيء على الرفوف. وبالمناسبة ، ستكون الصورة مفيدة أيضًا للانتقال إلى هناك ، بحيث يكون كل شيء في مكان واحد ، ولكن هذه تفاهات.


الآن ، وبعد اختيار مسار واحد - منحنى حول المبنى ، يمكنك تحديد كل منهم باستخدام Ctrl + A أو قائمة> تحرير> تحديد الكل وتعديله في نفس الوقت. تحتاج إلى رسمها جميعًا في نافذة التعبئة والسكتة الدماغية ، وفي الوقت نفسه إزالة الحد الإضافي هناك. حسنا ، أو إضافته إذا كنت في حاجة إليها لأسباب التصميم.



من المنطقي أن ترسم كل الألوان مع بعض الألوان مع الحد الأدنى من قيمة العتامة بالنسبة لهم ، حتى لو لم يكن ذلك ضروريًا من حيث التصميم. الحقيقة هي أن المتصفحات الذكية تعتقد أنه لا يمكنك النقر على مسار فارغ ، ولكن على مسار غمرته المياه - يمكنك ذلك ، حتى لو لم يرها أحد هذه التعبئة.

في مثالنا ، سنترك القليل من الضوء الأبيض لنرى بشكل أفضل المباني التي نعمل معها ، ونوفر كل شيء وننتقل بسلاسة إلى المتصفح ومحرر الشفرة الأكثر دراية.


مثال أساسي


لنقم بإنشاء صفحة html فارغة ، والصق ملف SVG الناتج مباشرة في ذلك وإضافة بعض CSS حتى لا يخرج أي شيء من الشاشة. لا يوجد شيء للتعليق عليه.


.map { width: 90%; max-width: 1300px; margin: 2rem auto; border: 1rem solid #fff; border-radius: 1rem; box-shadow: 0 0 .5rem rgba(0, 0, 0, .3); } .map > svg { width: 100%; height: auto; border-radius: .5rem; } 

نذكر أننا قمنا بإضافة فئات إلى المباني واستخدامها بحيث يكون CSS منظمًا إلى حد ما.


 .building { transition: opacity .3s ease-in-out; } .building:hover { cursor: pointer; opacity: .8 !important; } .building.-available { fill: #0f0 !important; } .building.-reserved { fill: #f00 !important; } .building.-service { fill: #fff !important; } 

نظرًا لأننا قمنا بتعريف الأنماط المضمنة في Inkscape ، فإننا نحتاج إلى مقاطعتها في CSS. هل سيكون أكثر ملاءمة للقيام بكل شيء في CSS؟ نعم ولا. ذلك يعتمد على الوضع. في بعض الأحيان لا يوجد خيار. على سبيل المثال ، إذا كان المصمم قد رسم الكثير من كل شيء غني بالألوان ولف كل شيء في CSS ، وقام بتضخيمه إلى استحالة عدم الاستغناء بطريقة أو بأخرى. في هذا المثال ، أستخدم خيار "غير مريح" لإظهار أنه ليس مخيفًا بشكل خاص في سياق المشكلة التي يتم حلها.



لنفترض أننا تلقينا بيانات جديدة عن المنازل ، وأضفنا فئات مختلفة إليها ، اعتمادًا على وضعها الحالي:


 const data = { id_0: { status: 'service' }, id_1: { status: 'available' }, id_2: { status: 'reserved' }, id_3: { status: 'available' }, id_4: { status: 'available' }, id_5: { status: 'reserved' }, messages: { 'available': '  ', 'reserved': '', 'service': '  1-2 ' } }; const map = document.getElementById('my-map'); const buildings = map.querySelectorAll('.building'); for (building of buildings) { const id = building.getAttribute('data-building-id'); const status = data[`id_${id}`].status; building.classList.add(`-${status}`); } 

نحصل على شيء مثل هذا:



شيء مشابه لما نحتاج إليه مرئي بالفعل. في هذه المرحلة ، قمنا بتمييز الكائنات على التضاريس التي تستجيب لتحويم الماوس. وليس من الصعب أن تضيف لهم استجابة بنقرة ماوس من خلال addEventListener القياسي.


خط الزعيم


غالبًا ما تكون هناك مهمة لإنشاء خطوط تربط الكائنات الموجودة على الخريطة وبعض العناصر في الصفحة بمعلومات إضافية ، بالإضافة إلى عمل نصائح بسيطة عند التمرير فوق هذه الكائنات نفسها. لحل هذه المشكلات ، تعد المكتبة المصغرة للزعيمين مناسبة تمامًا ، مما يخلق سهام متجهة لكل ذوق ولون.


دعنا نضيف أسعار تلميحات الأدوات إلى البيانات ورسم هذه الخطوط.


 const data = { id_0: { price: '3000', status: 'service' }, id_1: { price: '3000', status: 'available' }, id_2: { price: '2000', status: 'reserved' }, id_3: { price: '5000', status: 'available' }, id_4: { price: '2500', status: 'available' }, id_5: { price: '2500', status: 'reserved' }, messages: { 'available': '  ', 'reserved': '', 'service': '   (1-2 )' } }; const map = document.getElementById('my-map'); const buildings = map.querySelectorAll('.building'); const info = map.querySelector('.info'); const lines = []; for (building of buildings) { const id = building.getAttribute('data-building-id'); const status = data[`id_${id}`].status; const price = data[`id_${id}`].price; building.classList.add(`-${status}`); const line = new LeaderLine( LeaderLine.pointAnchor(building, { x: '50%', y: '50%' }), LeaderLine.pointAnchor(info, { x: '50%', y: 0 }), { color: '#fff', startPlug: 'arrow1', endPlug: 'behind', endSocket: 'top' } ); lines.push(line); } 

كما ترون ، لا شيء معقد يحدث. يحتوي الخط على "نقاط ربط" بالعناصر. تكون إحداثيات هذه النقاط بالنسبة للعناصر ملائمة لتحديد النسبة المئوية. بشكل عام ، هناك العديد من الخيارات المختلفة ، ليس من المنطقي إدراجها وتذكرها ، لذلك أوصي فقط بالبحث في الوثائق. ستكون هناك حاجة إلى أحد هذه الخيارات - startLabel - لإنشاء أداة صغيرة مع سعر.


 const line = new LeaderLine( LeaderLine.pointAnchor(building, { x: '50%', y: '50%' }), LeaderLine.pointAnchor(info, { x: '50%', y: 0 }), { startLabel: LeaderLine.captionLabel(`${price}/`, { fontFamily: 'Rubik Mono One', fontWeight: 400, offset: [-30, -50], outlineColor: '#555' }), color: '#fff', startPlug: 'arrow1', endPlug: 'behind', endSocket: 'top', hide: true } ); 

لا أحد يكترث برسم كل النصائح في محرر رسومي. إذا كان من المفترض أن يكون لديهم محتوى ثابت ، فيمكن أن يكون مناسبًا. خاصة إذا كانت هناك رغبة في أن تطلب منهم مواقف مختلفة لكائنات مختلفة.

يمكننا أيضًا إضافة خيار إخفاء بحيث لا يتم عرض جميع الخطوط كمكنسة. سنعرضها مرة واحدة في كل مرة تحوم فيها فوق المباني التي تتوافق معها:


 building.addEventListener('mouseover', () => { line.show(); }); building.addEventListener('mouseout', () => { line.hide(); }); 

هنا يمكنك عرض معلومات إضافية (في حالتنا ، ببساطة الحالة الحالية للكائن) في مكان المعلومات. سوف تتحول تقريبا ما هو مطلوب:



نادراً ما يتم تصميم مثل هذه الأشياء للأجهزة المحمولة ، ولكن تجدر الإشارة إلى أنها غالبًا ما تكون ممتلئة على سطح المكتب ، وحتى مع وجود بعض اللوحات على الجانب للحصول على معلومات إضافية وتحتاج إلى تمديد كل شيء بشكل جميل. شيء مثل هذا على سبيل المثال:


 svg { width: 100%; height: 100%; } 

في هذه الحالة ، بالتأكيد لن تتطابق نسب عنصر SVG مع نسب الصورة في الداخل. ماذا تفعل؟


أبعاد غير متطابقة


أول ما يتبادر إلى الذهن هو خاصية التهيئة: خاصية الغلاف من CSS. ولكن هناك نقطة واحدة: إنها غير قادرة تمامًا على العمل مع SVG. وحتى إذا نجحت ، يمكن أن تخرج المنازل الواقعة على أطراف الخطة من أطراف المخطط وتصبح غير قابلة للوصول تمامًا. حتى هنا تحتاج إلى الذهاب بطريقة أكثر تعقيدا قليلا.


الخطوة الأولى يحتوي SVG على سمة preserveAspectRatio ، والتي تشبه إلى حد ما خاصية ملائمة الكائنات (ليس تمامًا ، بالطبع ، ولكن ...). من خلال preserveAspectRatio="xMinYMin slice" لعنصر SVG الرئيسي في خطتنا ، نحصل على دائرة ممتدة بدون فراغات عند الحواف وبدون تشويه.


الخطوة الثانية تحتاج إلى إجراء السحب والإفلات باستخدام الماوس. من الناحية الفنية ، لا يزال لدينا مثل هذه الفرصة. هنا المهمة أكثر تعقيدًا ، خاصة للمبتدئين. من الناحية النظرية ، لدينا أحداث قياسية للماوس وشاشة تعمل باللمس يمكن معالجتها والحصول على قيمة مقدار نقل الخريطة. لكن في الممارسة العملية ، يمكنك الانخراط في هذا لفترة طويلة جدًا. سيأتي Hammer.js إلى الإنقاذ - مكتبة صغيرة أخرى تأخذ المطبخ الداخلي بأكمله على نفسها وتوفر واجهة بسيطة للعمل مع السحب والإفلات والسحب وغير ذلك


نحن بحاجة إلى نقل الطبقة مع المباني والصورة في جميع الاتجاهات. اجعلها سهلة:


 const buildingsLayer = map.querySelector('.buildings_layer'); const hammertime = new Hammer(buildingsLayer); hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); 

بشكل افتراضي ، تتضمن hammer.js أيضًا التعرف على svaypas ، لكننا لسنا في حاجة إليها على الخريطة ، لذا أوقف تشغيلها على الفور حتى لا تخدع رؤوسنا:


 hammertime.get('swipe').set({ enable: false }); 

أنت الآن بحاجة إلى فهم ما تحتاجه بالضبط للنشر لنقل الخريطة فقط إلى حوافها ، ولكن ليس أكثر من ذلك. من خلال تمثيل بسيط لمستطيلين في رأسنا ، نحن نفهم أننا بحاجة إلى معرفة المسافة البادئة للطبقة مع المباني من العنصر الأصل (SVG في حالتنا) من الجوانب الأربعة. يأتي GetBoundingClientRect إلى عملية الإنقاذ:


 const layer = buildingsLayer.getBoundingClientRect(); const parent = svg.getBoundingClientRect(); const offsets = { top: layer.top - parent.top, bottom: layer.bottom - parent.bottom, right: layer.right - parent.right, left: layer.left - parent.left, }; 

ولماذا لا يزال لدينا طريقة أكثر تحضرا (وعملية مستقرة) للقيام بذلك؟ يعد سحب getBoundingClientRect في كل مرة أمرًا سيئًا للغاية من حيث الأداء ، لكن الخيار ليس غنيًا للغاية ، ويكاد يكون من المستحيل ملاحظة تثبيط ، لذلك لن نتوصل إلى تحسينات سابقة لأوانها حيث يعمل كل شيء بشكل جيد. بطريقة أو بأخرى ، يتيح لنا ذلك فحص موضع الطبقة مع المباني ونقل كل شيء فقط إذا كان ذلك منطقيًا:


 let translateX = 0; let translateY = 0; hammertime.on('pan', (e) => { const layer = buildingsLayer.getBoundingClientRect(); const parent = svg.getBoundingClientRect(); const offsets = { top: layer.top - parent.top, bottom: layer.bottom - parent.bottom, right: layer.right - parent.right, left: layer.left - parent.left, }; const speedX = e.velocityX * 10; const speedY = e.velocityY * 10; if (speedX > 0 && offsets.left < 0) { //  } else if (speedX < 0 && offsets.right > 0) { //  } if (speedY > 0 && offsets.top < 0) { //  } else if (speedY < 0 && offsets.bottom > 0) { //   } buildingsLayer.setAttribute('transform', `translate(${translateX} ${translateY})`); }); 

عند الحواف ، عادة ما يكون الأمر يستحق التباطؤ حتى لا تكون هناك نقاط توقف أو هزات مفاجئة. وهكذا ، كل شيء يذهب جيئة وذهابا إلى شيء مثل هذا:


 if (speedX < -offsets.left) { translateX += speedX; } else { translateX += -offsets.left * speedX / 10; } 

هناك العديد من الخيارات للتباطؤ. هذا هو الأسهل. ونعم ، ليست جميلة ، لكنها غبية كفلاء وواضحة. عادة ما يتم اختيار المعاملات في مثل هذه الأمثلة بالعين ، وهذا يتوقف على السلوك المطلوب للبطاقة.


إذا قمت بفتح متصفح واللعب باستخدام حجم النافذة في أدوات المطور ، يمكنك العثور على خطأ ما ...


قوات النجاسة


على أجهزة سطح المكتب ، كل شيء يعمل ، ولكن على الهاتف المحمول ، يحدث السحر ، أي أنه بدلاً من نقل الخريطة ، يتحرك عنصر الجسم. س س س س س س س س-UUUU! فقط يلقي هناك لا يكفي. على الرغم من حسنًا ، يحدث هذا لأن هناك شيء ما يفيض في مكان ما ولم يتم تعيين بعض الكتابة الفوقية: مخفي. ولكن في حالتنا ، قد يحدث أن لا شيء يتحرك على الإطلاق.


لغز للأحرف الخضراء: يوجد عنصر g ، داخل عنصر svg ، داخل عنصر div ، داخل عنصر الجسم ، داخل عنصر html. Doctype بشكل طبيعي أتش تي أم أل. إذا قمت بإضافة تحويل: ترجم (...) إليه لسحب عنصر g ، ثم سيتحرك على الكمبيوتر المحمول ، كما كان الغرض منه ، ولكن على الهاتف لن يتحرك. لا توجد أخطاء في وحدة التحكم. ولكن هناك بالتأكيد خطأ. المتصفح هو آخر كروم هناك وهناك. السؤال هو لماذا؟

أقترح عليك التفكير في حوالي 10 دقائق بدون Google قبل مشاهدة الإجابة.



الجواب

ها ها ها! لقد خدعتك. بتعبير أدق ، ليس كذلك. وصفت ما نلاحظه مع الاختبار اليدوي. ولكن في الواقع ، كل شيء يعمل كما ينبغي. هذه ليست خطأ ، ولكنها ميزة مرتبطة بخاصية CSS لإجراءات اللمس . في سياق مهمتنا (فجأة!) تم الكشف عن وجودها ، علاوة على ذلك ، لها قيمة معينة تحطّم منطق التفاعل الكامل مع الخريطة. لذلك نحن نتعامل معه بوقاحة شديدة:


 svg { touch-action: none !important; } 

لكن عدنا إلى الأغنام وننظر إلى النتيجة (من الأفضل ، بالطبع ، فتح علامة تبويب منفصلة):



قررت عدم تخصيص الكود لأي من الأطر العصرية ، بحيث يظل في شكل فراغ محايد بدون شكل ، والذي يمكنك البناء عليه عند إنشاء مكوناتك.


ما هي النتيجة؟


بعد أن قضيت وقتًا طويلاً ، وضعنا خطة توجد عليها صورة نقطية ، تسليط الضوء على تفاصيلها المختلفة ، وربط الكائنات غير المرتبطة بالسهام وردود الفعل على الماوس. آمل أن أتمكن من إيصال الفكرة الأساسية عن كيفية القيام بكل هذا في إصدار "الميزانية". كما لاحظنا في بداية المقال ، هناك العديد من التطبيقات المختلفة ، بما في ذلك تلك التي لا تتعلق بنوع من مواقع التصميم المشوشة (على الرغم من أن هذا النهج يستخدم في كثير من الأحيان عليها). حسنًا ، إذا كنت تبحث عن شيء تقرأه حول الأشياء التفاعلية ، ولكن بالفعل ثلاثية الأبعاد ، فأنا أترك رابطًا لمقال حول الموضوع - العروض التقديمية ثلاثية الأبعاد للمنتج على Three.js لأصغرها .

Source: https://habr.com/ru/post/ar478698/


All Articles