
Di Toaster, mereka sering bertanya tentang cara membuat diagram interaktif rumah, rencana struktur internal, kemampuan untuk memilih lantai atau apartemen dengan informasi tentang mereka, menampilkan informasi tentang detail produk tertentu ketika Anda mengarahkan mouse ke foto, dll. Ini bukan tentang model tiga dimensi, tetapi tentang gambar dengan kemampuan untuk menyoroti detail tertentu. Semua tugas ini mirip dan diselesaikan dengan cukup sederhana, namun pertanyaan tetap muncul, jadi hari ini kita akan melihat bagaimana hal-hal seperti itu dilakukan dengan menggunakan SVG, editor grafis dan sejumput javascript.
Pilihan SVG disebabkan oleh fakta bahwa ini adalah opsi paling sederhana untuk pengembangan dan debugging. Saya bertemu orang-orang yang menyarankan semua ini dilakukan di atas kanvas, tetapi jauh lebih sulit untuk memahami apa yang terjadi, dan koordinat semua titik pada kurva perlu dihitung terlebih dahulu, tetapi di sini saya membuka alat pengembang dan segera melihat seluruh struktur, semua benda, dengan yang ada interaksi, dan segala sesuatu yang lain dapat diklik dengan mouse di antarmuka yang ramah manusia. Kinerja antara kanvas 2d biasa dan SVG tidak akan berbeda. WebGL dapat memberikan beberapa bonus dalam hal ini, tetapi jadwal pengembangan akan meningkat secara signifikan, belum lagi dukungan lebih lanjut, yang tidak selalu sesuai dengan anggaran. Saya bahkan akan mengatakan "tidak pernah cocok."
Dalam tutorial ini, kami akan melakukan sesuatu seperti widget untuk situs fiktif untuk menyewa rumah pribadi di beberapa area, tetapi jelas bahwa prinsip-prinsip untuk membuat hal-hal seperti itu berlaku untuk subjek apa pun.
Memulai
Saya akan menunjukkan semuanya dengan Inkscape sebagai contoh, tetapi semua tindakan yang sama dapat dilakukan di editor lain menggunakan fungsi serupa.
Agar berfungsi, kita membutuhkan dua kotak dialog utama:
- Editor XML (Ctrl + Shift + X atau ikon dengan kurung sudut) - untuk melihat struktur dokumen dalam bentuk markup dan mengedit elemen individual.
- Isi dan Stroke (Ctrl + Shift + F atau ikon dengan kuas di bingkai) - terutama untuk mengisi garis besar.
Segera luncurkan dan lanjutkan ke pembuatan dokumen.
Jika Anda secara tidak sengaja menyeretnya ke jendela terpisah, maka Anda dapat mengklik di bawah bingkai atas jendela ini (di mana tidak ada), dan seret kembali ke jendela utama. Ini tidak sepenuhnya intuitif, tetapi lebih nyaman.
Buka foto dengan tampilan area. Kita dapat memilih untuk memasukkan gambar itu sendiri sebagai string base64 atau tautan eksternal. Karena besar, pilih tautannya. Kemudian kita akan mengubah jalur ke gambar dengan tangan kita ketika memperkenalkan semuanya ke halaman situs. Dokumen SVG dibuat di mana foto akan disematkan melalui tag gambar.
Untuk gambar raster yang disematkan dalam SVG, tertanam dalam HTML, Anda dapat menggunakan lazy loading, serta untuk gambar biasa di halaman. Dalam contoh ini, kita tidak akan berkutat pada optimisasi semacam itu, tetapi jangan lupakan mereka dalam pekerjaan praktis.
Pada tahap saat ini, kita melihat sesuatu di depan kita seperti ini:

Sekarang buat layer baru (Ctrl + Shift + N atau Menu> Layer> Add layer). Di editor XML, kita melihat bahwa elemen g biasa telah muncul. Meskipun kami belum melangkah jauh, kami dapat memberikan kelas, yang akan kami gunakan nanti dalam skrip.
Jangan mengandalkan id. Semakin kompleks antarmuka, semakin mudah membuat mereka mengulangi dan mendapatkan bug aneh. Dan dalam tugas kami, mereka masih tidak mendapat manfaat. Jadi atribut kelas atau data adalah pilihan kami.
Jika Anda melihat lebih dekat pada struktur dokumen di editor XML, Anda akan melihat bahwa ada banyak yang berlebihan. Editor grafis vektor yang kurang lebih kompleks akan menambahkan sesuatu sendiri ke dokumen. Untuk menghapus semua ini dengan tangan Anda adalah tugas yang panjang dan tanpa rasa terima kasih, editor akan terus menambahkan sesuatu lagi. Jadi membersihkan SVG dari sampah hanya dilakukan di akhir pekerjaan. Dan lebih disukai dalam bentuk otomatis, karena ada opsi siap pakai, svgo yang sama misalnya.
Temukan alat yang disebut Draw Bezier curves dan Straight Lines (Shift + F6). Dengan itu, kita akan menggambar kontur tertutup di sekitar objek. Dalam tugas kita, kita perlu menjabarkan semua bangunan. Sebagai contoh, kita membatasi diri kita menjadi enam, tetapi dalam kondisi nyata itu akan bermanfaat untuk pra-mengalokasikan waktu untuk secara akurat menguraikan semua objek yang diperlukan. Meskipun sering terjadi bahwa ada banyak entitas yang serupa, lantai yang sama pada sebuah bangunan dapat benar-benar identik. Dalam kasus seperti itu, Anda dapat mempercepat sedikit dan menyalin-lekatkan kurva.
Setelah kami mengelilingi bangunan yang diperlukan, kami kembali ke editor XML, menambahkan kelas atau, kemungkinan besar, akan lebih nyaman, atribut data dengan indeks untuk mereka (mungkin dengan alamat, tetapi karena kami memiliki area fiktif, hanya ada indeks), dan pindahkan semuanya ke lapisan yang sebelumnya dibuat sehingga semuanya "diletakkan di rak". Dan gambar itu, omong-omong, juga akan berguna untuk pindah ke sana, sehingga semuanya ada di satu tempat, tetapi ini adalah hal-hal sepele.
Sekarang, setelah memilih satu jalur - kurva di sekitar gedung, Anda dapat memilih semuanya dengan Ctrl + A atau Menu> Edit> Pilih Semua dan edit pada saat yang sama. Anda harus mengecat semuanya di jendela Fill and Stroke, dan pada saat yang sama hapus stroke tambahan di sana. Baik, atau tambahkan jika Anda membutuhkannya untuk alasan desain.

Masuk akal untuk melukis semua kontur dengan beberapa warna dengan nilai opacity minimum untuk mereka, bahkan jika ini tidak diperlukan dalam hal desain. Faktanya adalah peramban pintar percaya bahwa Anda tidak dapat mengeklik pada jalur kosong, tetapi pada jalur yang dibanjiri - Anda dapat, bahkan jika tidak ada yang melihat isi ini.
Dalam contoh kami, kami akan meninggalkan sedikit sorotan dalam warna putih untuk melihat dengan lebih baik bangunan tempat kami bekerja, menyimpan semuanya dan dengan lancar pindah ke browser dan editor kode yang lebih akrab.
Contoh dasar
Mari kita buat halaman html kosong, tempel langsung SVG yang dihasilkan ke dalamnya dan tambahkan beberapa CSS sehingga tidak ada yang keluar dari layar. Tidak ada yang perlu dikomentari.
.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; }
Kami ingat bahwa kami menambahkan kelas ke bangunan dan menggunakannya agar CSS kurang lebih terstruktur.
.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; }
Karena kita mendefinisikan gaya sebaris di Inkscape, kita perlu menyela mereka dalam CSS. Apakah akan lebih mudah untuk melakukan semuanya dalam CSS? Ya dan tidak Itu tergantung situasi. Terkadang tidak ada pilihan. Sebagai contoh, jika seorang desainer menggambar banyak semuanya berwarna-warni dan membungkus semuanya dalam CSS dan mengembangnya menjadi tidak mungkin entah bagaimana tidak kome il faut. Dalam contoh ini, saya menggunakan opsi "tidak nyaman" untuk menunjukkan bahwa itu tidak terlalu menakutkan dalam konteks masalah yang sedang dipecahkan.

Misalkan kami menerima data baru tentang rumah, dan menambahkan kelas yang berbeda kepada mereka, tergantung pada status mereka saat ini:
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}`); }
Kami mendapatkan sesuatu seperti ini:

Sesuatu yang mirip dengan yang kita butuhkan sudah terlihat. Pada tahap ini, kami telah menyorot objek di medan yang merespons ke kursor mouse. Dan tidak sulit untuk menambahkan respons mereka terhadap klik mouse melalui addEventListener standar.
Garis pemimpin
Seringkali ada tugas membuat garis yang akan menghubungkan objek pada peta dan beberapa elemen pada halaman dengan informasi tambahan, serta membuat tooltip minimal ketika melayang di atas objek yang sama. Untuk mengatasi masalah ini, mini-library leader-line sangat cocok, yang menciptakan panah vektor untuk setiap selera dan warna.
Mari kita tambahkan harga untuk tooltips ke data dan menggambar garis-garis ini.
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); }
Seperti yang Anda lihat, tidak ada yang rumit terjadi. Garis memiliki "titik lampiran" ke elemen. Koordinat titik-titik ini relatif terhadap unsur-unsur biasanya nyaman untuk ditentukan dalam persen. Secara umum, ada banyak pilihan berbeda, tidak masuk akal untuk mendaftar dan mengingat, jadi saya sarankan hanya melihat melalui dokumentasi. Salah satu opsi ini - startLabel - akan diperlukan bagi kita untuk membuat tooltip kecil dengan harga.
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 } );
Tidak ada yang mengganggu untuk menggambar semua tips dalam editor grafis. Jika mereka seharusnya memiliki konten yang konsisten, maka itu bahkan bisa nyaman. Terutama jika ada keinginan untuk meminta mereka posisi yang berbeda untuk objek yang berbeda.
Kami juga dapat menambahkan opsi sembunyikan sehingga semua baris tidak ditampilkan sebagai sapu. Kami akan menunjukkan satu per satu saat Anda mengarahkan kursor ke bangunan yang sesuai:
building.addEventListener('mouseover', () => { line.show(); }); building.addEventListener('mouseout', () => { line.hide(); });
Di sini Anda dapat menampilkan informasi tambahan (dalam kasus kami, cukup status objek saat ini) di tempat untuk informasi. Ini akan menghasilkan hampir apa yang dibutuhkan:

Hal-hal semacam itu jarang dirancang untuk perangkat seluler, tetapi perlu diingat bahwa mereka sering dibuat layar penuh pada desktop, dan bahkan dengan beberapa panel di samping untuk informasi tambahan dan Anda perlu meregangkan semuanya dengan indah. Sesuatu seperti ini misalnya:
svg { width: 100%; height: 100%; }
Dalam hal ini, proporsi elemen SVG pasti tidak akan bertepatan dengan proporsi gambar di dalamnya. Apa yang harus dilakukan
Proporsi yang tidak cocok
Hal pertama yang terlintas dalam pikiran adalah objek-fit: properti penutup dari CSS. Tetapi ada satu hal: itu sama sekali tidak dapat bekerja dengan SVG. Dan bahkan jika itu berhasil, maka rumah-rumah di sepanjang tepi rencana bisa keluar dari tepi skema dan menjadi benar-benar tidak dapat diakses. Jadi di sini Anda perlu pergi cara yang sedikit lebih rumit.
Langkah pertama. SVG memiliki atribut preserveAspectRatio , yang agak mirip dengan properti objek-fit (tidak cukup, tentu saja, tetapi ...). Dengan preserveAspectRatio="xMinYMin slice"
untuk elemen SVG utama dari rencana kami, kami mendapatkan rangkaian tambahan tanpa celah di tepinya dan tanpa distorsi.
Langkah Dua Anda harus melakukan drag dengan mouse. Secara teknis, kami masih memiliki kesempatan seperti itu. Di sini tugasnya lebih rumit, terutama untuk pemula. Secara teori, kami memiliki acara standar untuk mouse dan layar sentuh yang dapat diproses dan mendapatkan nilai berapa peta yang perlu dipindahkan. Namun dalam praktiknya, Anda bisa terjebak dalam hal ini untuk waktu yang sangat lama. Hammer.js akan datang untuk menyelamatkan - perpustakaan kecil lain yang mengambil seluruh dapur internal untuk dirinya sendiri dan menyediakan antarmuka sederhana untuk bekerja dengan drag dan drop, geser, dll.
Kita perlu memindahkan layer dengan bangunan dan gambar ke segala arah. Buat itu mudah:
const buildingsLayer = map.querySelector('.buildings_layer'); const hammertime = new Hammer(buildingsLayer); hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
Secara default, hammer.js juga mencakup pengenalan svaypa, tetapi kami tidak membutuhkannya di peta, jadi matikan segera sehingga tidak membodohi kepala kami:
hammertime.get('swipe').set({ enable: false });
Sekarang Anda perlu memahami apa yang sebenarnya Anda poskan untuk memindahkan peta hanya ke tepinya, tetapi tidak lebih jauh. Dengan representasi sederhana dari dua persegi panjang di kepala kita, kita memahami bahwa untuk ini kita perlu mengetahui lekukan lapisan dengan bangunan dari elemen induk (SVG dalam kasus kita) dari keempat sisi. GetBoundingClientRect datang untuk menyelamatkan:
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, };
Dan mengapa kita masih tidak memiliki cara yang lebih beradab (dan stabil) untuk melakukan ini? Menarik getBoundingClientRect setiap kali sangat buruk dalam hal kinerja, tetapi pilihannya tidak terlalu kaya, dan hampir tidak mungkin untuk melihat penghambatan, jadi kami tidak akan datang dengan optimasi prematur di mana semuanya bekerja dengan baik. Dengan satu atau lain cara, ini memungkinkan kita untuk memeriksa posisi layer dengan bangunan dan memindahkan semuanya hanya jika masuk akal:
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) {
Pada bagian tepinya, biasanya perlu diperlambat agar tidak ada penghentian atau sentakan mendadak. Dengan demikian, semuanya bolak-balik menjadi seperti ini:
if (speedX < -offsets.left) { translateX += speedX; } else { translateX += -offsets.left * speedX / 10; }
Ada banyak opsi untuk pelambatan. Yang ini paling mudah. Dan ya, itu tidak terlalu indah, tetapi bodoh seperti gabus dan jelas. Koefisien dalam contoh tersebut biasanya dipilih berdasarkan mata, tergantung pada perilaku kartu yang diinginkan.
Jika Anda membuka browser dan bermain dengan ukuran jendela di alat pengembang, Anda dapat menemukan bahwa ada yang tidak beres ...
Kekuatan yang tidak murni
Pada perangkat desktop, semuanya berfungsi, tetapi pada ponsel, keajaiban terjadi, yaitu, alih-alih memindahkan peta, elemen tubuh bergerak. Oooooooo! Hanya para pemain di sana tidak cukup. Meskipun baik-baik saja, ini terjadi karena ada sesuatu yang meluap di suatu tempat dan beberapa menimpa tidak disetel meluap: disembunyikan. Tetapi dalam kasus kami, mungkin saja terjadi bahwa tidak ada yang bergerak sama sekali.
Teka-teki untuk huruf hijau: ada elemen g, di dalam elemen svg, di dalam elemen div, di dalam elemen tubuh, di dalam elemen html. Doctype secara alami html. Jika Anda menambahkan transform: translate (...) ke dalamnya untuk menyeret elemen g, maka pada laptop itu akan bergerak, seperti yang dimaksudkan, tetapi pada telepon itu bahkan tidak akan bergerak. Tidak ada kesalahan di konsol. Tapi pasti ada bug. Browser adalah Chrome terakhir di sana dan di sana. Pertanyaannya adalah mengapa?
Saya sarankan Anda berpikir sekitar 10 menit tanpa Google sebelum menonton jawabannya.

JawabannyaHaha Aku menipu kamu. Lebih tepatnya, tidak begitu. Saya menggambarkan apa yang akan kami amati dengan pengujian manual. Namun dalam kenyataannya, semuanya berjalan sebagaimana mestinya. Ini bukan bug, tetapi fitur yang terkait dengan properti CSS dari tindakan sentuh . Dalam konteks tugas kita (tiba-tiba!) Terungkap bahwa itu ada, dan, apalagi, memiliki nilai tertentu yang mematahkan seluruh logika interaksi dengan peta. Jadi kami memperlakukannya dengan sangat kasar:
svg { touch-action: none !important; }
Tetapi kembalilah ke domba-domba kita dan lihat hasilnya (lebih baik, tentu saja, membuka di tab terpisah):
Saya memutuskan untuk tidak mengkustomisasi kode untuk kerangka kerja yang modis, sehingga tetap dalam bentuk kosong tanpa bentuk netral, dari mana Anda dapat membangun saat membuat komponen Anda.
Apa hasilnya?
Setelah menghabiskan sedikit waktu, kami membuat rencana di mana ada gambar raster, menyoroti berbagai detailnya, menghubungkan benda-benda yang tidak terkait dengan panah dan reaksi terhadap mouse. Saya berharap saya berhasil menyampaikan ide dasar tentang bagaimana semua ini dilakukan dalam versi "anggaran". Seperti yang kami catat di awal artikel, ada banyak aplikasi yang berbeda, termasuk yang tidak terkait dengan beberapa jenis situs desain yang membingungkan (meskipun pendekatan ini sangat sering digunakan pada mereka). Nah, jika Anda mencari sesuatu untuk dibaca tentang hal yang interaktif, tetapi sudah tiga dimensi, maka saya meninggalkan tautan ke artikel tentang topik - Presentasi produk tiga dimensi pada Three.js untuk yang terkecil .