Indikator pemuatan SVG di Vue.js

Hai Saya belajar di front-end, dan secara paralel, dalam proyek pelatihan, saya mengembangkan SPA di Vue.js untuk back-end, yang mengumpulkan data dari bot pencarian. Bot menghasilkan dari 0 hingga 500 entri, dan saya harus: mengunggah, mengurutkan berdasarkan kriteria yang ditentukan, tampil di tabel.


Baik back-end maupun bot tidak dapat mengurutkan data, jadi saya harus mengunduh semua data dan memprosesnya di sisi browser. Penyortiran sangat cepat, tetapi kecepatan unduhan tergantung pada koneksi, dan 500 catatan yang ditunjukkan dapat dimuat dari 10 hingga 40 detik.


Pada awalnya, saat memuat, saya menunjukkan spiner, kekurangannya adalah bahwa pengguna tidak tahu kapan unduhan akan berakhir. Dalam kasus saya, jumlah catatan yang ditemukan bot diketahui sebelumnya, sehingga Anda dapat menunjukkan berapa banyak catatan yang dimuat.


Untuk mencerahkan menunggu pengguna, saya memutuskan untuk menunjukkan kepadanya proses pemuatan:


  1. digit - berapa% catatan yang sudah dimuat
  2. jadwal - memuat waktu setiap catatan
  3. mengisi -% beban. Karena grafik mengisi blok persegi panjang saat memuat, jelas bagian mana dari blok yang masih harus diisi

Inilah animasi hasil yang saya inginkan dan dapatkan:



... menurut saya, ternyata lucu.


Dalam artikel ini saya akan menunjukkan kepada Anda bagaimana bergerak menuju hasil langkah demi langkah. Saya tidak menggambar grafik fungsi di browser sebelum desa, jadi pengembangan indikator membawakan saya pengetahuan yang sederhana namun baru tentang penggunaan SVG dan Vue.



Memilih Metode Canvas atau SVG Rendering


Saya menggunakan Canvas dalam permainan ular sederhana di JS, dan SVG, dalam satu proyek, saya hanya memasukkannya ke dalam halaman dengan tag objek dan memperhatikan bahwa ketika penskalaan, gambar SVG selalu menjaga ketajaman (itu sebabnya itu adalah vektor) dan Canvas mengamati gambar buram. Berdasarkan pengamatan ini, saya memutuskan untuk menggambar grafik menggunakan SVG, karena Anda harus mulai kapan-kapan.


Rencana kerja


Berdasarkan kerangka Vue yang dipilih, dan metode pembentukan gambar yang dipilih menggunakan SVG, saya membuat sendiri rencana kerja berikut:


  1. Cari dan pelajari informasi tentang topik menggunakan SVG dengan Vue
  2. Eksperimen dengan pembentukan dan perubahan SVG dalam konteks Vue
  3. Indikator memuat prototipe
  4. Alokasi indikator pemuatan ke dalam komponen Vue yang terpisah
  5. Penerapan komponen dalam SPA

Memulai


  1. Membuat proyek kosong

    Saya telah vue cli diinstal . Untuk membuat proyek baru, pada prompt perintah saya memasukkan vue create loadprogresser , pilih pengaturan proyek secara default , proyek vue baru dibuat dengan nama loadprogresser, lalu saya menghapus yang tidak perlu dari itu:


    ApakahTelah menjadi

    Struktur proyek secara default



    Struktur setelah "pembersihan"



    Salam dari Vue


    <template> <div> <h1>Progresser</h1> </div> </template> <script> export default {name: 'app'} </script>; 

    Teks saya adalah "Progresser" di App.vue




  2. Cari dan pelajari informasi tentang topik menggunakan SVG bersama dengan Vue


    Situs hebat dengan info berguna tentang HTML, CSS, dan SVG css.yoksel.ru Contoh yang bagus dengan SVG tersedia di dokumentasi Vue sendiri. Contoh SVG-grafik dan tautan semacam itu. Berdasarkan bahan-bahan ini, templat komponen minimal dengan SVG lahir dari mana saya mulai:


     <template> <div class="wrapper"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> //svg    //   svg- </svg> </div> </template> 

  3. Eksperimen dengan pembentukan dan perubahan SVG dalam konteks Vue


    SVG rectangle rect

    rect - a rectangle, sosok paling sederhana. Saya membuat svg dengan dimensi 100x100px, dan menggambar persegi panjang dengan koordinat awal 25:25 dan ukuran 50x50 px, warna isi standar adalah hitam (tanpa gaya)



    Styling SVG dan arahkan kursor pseudo-class:

    Saya akan mencoba untuk gaya rectangle rect di svg. Untuk melakukan ini, saya menambahkan kelas "sample" ke svg, di bagian style dari file-vue saya menambahkan style .sample rect (saya mewarnai rectangular rectangle dengan yellow) dan .sample rect: hover yang membuat style elemen rect ketika Anda mengarahkan mouse:


    Kode sumber
     <template> <div id="app"> <svg class="sample" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px"> <rect x=25 y=25 width="50px" height="50px"/> </svg> </div> </template> <script> export default { name: 'app' } </script> <style> .sample rect { fill: yellow; stroke: green; stroke-width: 4; transition: all 350ms; } .sample rect:hover { fill: gray; } </style> 


    Implementasi JSfiddle


    Kesimpulan: svg sangat cocok dengan file template vue dan ditata dengan gaya yang ditentukan. Sebuah awal telah dibuat!


    Jalur SVG sebagai dasar indikator

    Pada bagian ini, saya akan mengganti rect dengan path, <path :d="D" class="path"/> pada atribut d dari tag path, saya akan meneruskan string D dengan koordinat path dari vue. Koneksi dilakukan melalui v-bind:d="D" , yang disingkat :d="D"


    Baris D = ā€œM 0 0 0 50 50 50 50 0 Zā€ menarik tiga garis dengan koordinat 0: 0-> 0: 50-> 50: 50-> 0:50 dan menutup kontur dengan perintah Z, membentuk kotak 50x50px mulai dari koordinasikan 0: 0. Menggunakan gaya jalur, bentuknya diberi warna isian kuning dan batas abu-abu 1px.


    Sumber PATH kuning
      <template> <div id="app"> <svg class="sample" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px"> <path :d="D" class="path"/> </svg> </div> </template> <script> export default { name: 'app', data(){ return { D:"M 0 0 0 50 50 50 50 0 Z" } } } </script> <style> .path { fill:yellow; stroke:gray; } </style> 


  4. Indikator memuat prototipe


    Dalam versi minimal, saya membuat diagram sederhana. Wadah svg dengan ketinggian 100px, lebar 400px dimasukkan dalam templat, tag path ditempatkan di dalam, ke atribut d yang saya tambahkan string path yang dihasilkan d dari data vue, yang pada gilirannya dibentuk dari array timePoints di mana satu dari 400 ditambahkan (setiap lebar wadah) angka acak dalam rentang dari 0 hingga 100. Semuanya sederhana, dalam kait siklus hidup yang dibuat, metode pembaruan disebut di mana titik (acak) baru ditambahkan ke diagram melalui metode addTime, kemudian metode getSVGTimePoints mengembalikan string untuk dikirim ke PATH, melalui setTimeout memulai kembali metode pembaruan



    Lebih lanjut tentang pembentukan string untuk PATH


    String untuk PATH dibentuk dalam metode getSVGTimePoints, dari array timePoints yang saya proses dengan pengurangan. Sebagai nilai awal dari pengurangan, saya menggunakan "M 0 0" (mulai dari koordinat 0: 0). Lebih lanjut dalam mengurangi, pasangan baru koordinat relatif dX dan dY akan ditambahkan ke baris. Huruf besar "l" (besar "L" menunjukkan koordinat absolut) bertanggung jawab untuk koordinat yang relatif, setelah d ditempatkan dX dan kemudian dY, dipisahkan oleh spasi. Dalam prototipe ini, dY = 1 (kenaikan demi 1px), di masa depan, di sepanjang sumbu X saya akan bergerak dengan kenaikan dX yang dihitung dari lebar wadah dan jumlah titik yang perlu ditempatkan di dalamnya. Di baris terakhir dari generasi PATH
    path +=`L ${this.timePoints.length} 0`
    Saya memaksakan, dari titik terakhir, saya selesai membangun garis ke sumbu X. Jika Anda perlu menutup kontur, Anda dapat menambahkan "Z" ke ujung garis, pada awalnya saya berpikir bahwa tanpa kontur tertutup, angka yang dihasilkan tidak akan diisi (isi), tetapi ternyata tidak di mana itu tidak ditutup, stroke - stroke tidak akan ditarik.


     getSVGTimePoints:function(){ let predY = 0 let path = this.timePoints.reduce((str, item)=>{ let dY = item - predY predY = item return str + `l 1 ${dY} ` },'M 0 0 ') path +=`L ${this.timePoints.length} 0`// Z`     return path }, 

    Saya akan terus melakukan perubahan. Indikator saya harus diskalakan dengan lebar dan tinggi sehingga semua titik yang ditransmisikan masuk ke dalam wadah yang diberikan. Untuk melakukan ini, buka DOM dan cari tahu dimensi wadah


    1. ref - mendapatkan informasi tentang elemen DOM

      Ke wadah div (di mana svg dimasukkan) saya menambahkan kelas pembungkus untuk melewati lebar dan tinggi melalui gaya. Dan agar svg memakan seluruh ruang kontainer, atur tinggi dan lebarnya menjadi 100%. RECT, pada gilirannya, juga akan menempati seluruh ruang kontainer dan akan menjadi latar belakang untuk PATH


       <div id="app" class="wrapper" ref="loadprogresser"> <svg id="sample" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> <rect x=0 y=0 width="100%" height="100%"/> <path :d="d" fill="transparent" stroke="black"/> </svg> </div> 

      Untuk menemukan wadah DIV saya di DOM Vue virtual, saya menambahkan atribut ref dan memberinya nama yang dengannya saya akan mencari ref="loadprogresser" . Dalam kait siklus hidup yang mounted , saya akan memanggil metode getScales (), di mana, dengan string const {width, height} = this.$refs.loadprogresser.getBoundingClientRect() saya menemukan lebar dan tinggi elemen DIV setelah muncul di DOM.


      Selanjutnya, perhitungan sederhana kenaikan di sepanjang sumbu X, tergantung pada lebar wadah dan jumlah titik yang ingin kita masukkan ke dalamnya. Skala di sepanjang sumbu Y dihitung ulang setiap kali maksimum ditemukan dalam nilai yang ditransmisikan.



    2. transform - ubah sistem koordinat

      Pada titik ini, saya perhatikan bahwa kita perlu mengubah sistem koordinat sehingga koordinat 0: 0 dimulai dari sudut kiri bawah, dan sumbu Y tumbuh ke atas, bukan ke bawah. Anda tentu saja dapat membuat perhitungan untuk setiap titik, tetapi SVG memiliki atribut transformasi yang memungkinkan Anda untuk mengubah koordinat.


      Dalam kasus saya, saya perlu menerapkan skala -1 ke koordinat Y (sehingga nilai-nilai Y diletakkan kembali), dan menggeser asal ke ketinggian wadah minus . Karena ketinggian wadah dapat berupa (diatur melalui gaya), kami harus membentuk garis transformasi koordinat dalam kait yang mounted dengan kode berikut: this.transform = `scale( 1, -1) translate(0,${-this.wrapHeight})`


      Tetapi transformasi yang diterapkan pada PATH saja tidak akan berfungsi, untuk ini Anda perlu membungkus PATH dalam grup (g tag) yang menerapkan transformasi koordinat:


       <g :transform="transform"> <path :d="d" fill="transparent" stroke="black"/> </g> 

      Hasilnya, koordinat terbalik dengan benar, indikator unduhan menjadi lebih dekat dengan desain



    3. Teks SVG dan pemusatan teks

      Teks diperlukan untuk menampilkan% memuat. Penempatan teks di tengah secara vertikal dan horizontal di SVG cukup sederhana untuk diatur (dibandingkan dengan HTML / CSS), atribut datang ke penyelamatan (saya segera menulis nilai-nilai) dominan-baseline = "pusat" dan teks-jangkar = "tengah"


      Teks dalam SVG ditampilkan dengan tag yang sesuai:


      <text x="50%" y="50%" dominant-baseline="central" text-anchor="middle">{{TextPrc}}</text>

      di mana TextPrc adalah pengikatan ke variabel terkait, dihitung dengan rasio sederhana dari jumlah poin yang diharapkan dengan jumlah yang ditransfer this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %` this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %` .


      Koordinat awal x = "50%" y = "50%" sesuai dengan pusat wadah, dan atribut dasar-jangkar dan teks-jangkar bertanggung jawab untuk teks yang disejajarkan secara vertikal dan horizontal.



      Hal-hal dasar pada topik telah dikerjakan, sekarang kita perlu memilih prototipe indikator dalam komponen terpisah.




  5. Alokasi indikator pemuatan ke dalam komponen Vue yang terpisah


    Untuk memulainya, saya akan menentukan data yang akan saya transfer ke komponen, ini akan menjadi: maxSamples - jumlah sampel dengan lebar 100%, dan Point - unit data (titik) yang akan dimasukkan ke dalam array titik (berdasarkan yang, setelah pemrosesan, akan dibentuk jadwal). Data yang dikirimkan ke komponen dari induk, saya letakkan di bagian alat peraga


     props:{ maxSamples: {//-   100%-  type: Number, default: 400 }, Point:{//  value:0 } } 

    Masalah reaktivitas

    Properti getPath yang dikomputasi bertanggung jawab atas fakta bahwa titik baru yang diteruskan ke komponen diproses, yang bergantung pada Point (dan jika ya, itu dihitung ulang ketika Point berubah)


      // ... <path :d="getPath"/> ... //  props:{ ... Point:{ value:0 } //  computed:{ getPath(){ this.addValue({value:this.Point.value}) return this.getSVGPoints()//this.d } }, 

    Pada awalnya saya membuat Point of type Number, yang logis, tetapi kemudian tidak semua titik diproses, tetapi hanya berbeda dari yang sebelumnya. Misalnya, jika hanya nomor 10 yang ditransfer dari induk ke suatu titik, maka hanya satu titik yang akan ditarik pada bagan, semua yang berikutnya akan diabaikan karena mereka tidak berbeda dari yang sebelumnya.


    Mengganti tipe Point dari Number dengan objek {value: 0} mengarah ke hasil yang diinginkan - getPath properti yang dikomputasi () sekarang memproses setiap titik yang ditransmisikan, melalui Point.value Saya meneruskan nilai poin tersebut


    Sumber komponen Progresser.vue
     <template> <div class="wrapper" ref="loadprogresser"> <svg class="wrapper__content" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" > <g :transform="transform"> <path :d="getPath"/> </g> <text x="50%" y="50%" dominant-baseline="central" text-anchor="middle"> {{TextPrc}} </text> </svg> </div> </template> <script> export default { props:{ maxSamples: {//-   100%-  type: Number, default: 400 }, Point:{ value:0 } }, data(){ return { Samples:0,//   wrapHeight:100,//      maxY:0,//  Y  (  ) scales:{ w:1,//   (   ) h:1 //   //(   Y   ) }, Points:[],//     (  Point.value) transform:'scale( 1, -1) translate(0,0)', TextPrc: '0%' } }, mounted: function(){ this.getScales() }, methods:{ getScales(){ const {width, height} = this.$refs.loadprogresser.getBoundingClientRect() //.    this.scales.w = width / this.maxSamples //  Y    this.wrapHeight = height this.transform = `scale( 1, -1) translate(0,${-this.wrapHeight}) rotate(0)` }, getVScale(){//    this.scales.h = (this.maxY == 0)? 0 : this.wrapHeight / this.maxY }, //          getYMax({value = 0}){ this.maxY = (value > this.maxY) ? value : this.maxY }, addValue({value = 0}){ if (this.Samples < this.maxSamples) { this.getYMax({value}) this.getVScale() this.Points.push(value) // Int this.Samples ++; this.TextPrc = `${((this.Samples * 100)/this.maxSamples) | 0} %` } }, getSVGPoints(){ //    Path let predY = 0 let path = this.Points.reduce((str, item)=>{ let dY = (item - predY) * this.scales.h predY = item return str + `l ${this.scales.w} ${dY} ` },'M 0 0 ') path +=`L ${this.Points.length * this.scales.w} 0 `// Z`     return path }, }, computed:{ getPath(){ this.addValue({value:this.Point.value})//   return this.getSVGPoints()//this.d -  SVG PATH } } } </script> <style scoped> .wrapper { width: 400px;/*     */ height: 100px;/*  ( )   */ font-size: 4rem; font-weight: 600; border-left: 1px gray solid; border-right: 1px gray solid; overflow: hidden; } .wrapper__content path { opacity: 0.5; fill: lightgreen; stroke: green; stroke-width: 1; } .wrapper__content text { opacity: 0.5; } </style> 


    Memanggil dari komponen induk dan melewati parameter

    Untuk bekerja dengan komponen, Anda perlu mengimpornya ke komponen induk
    import Progresser from "./components/Progresser"
    dan menyatakan dalam bagian
    components: {Progresser }


    Dalam templat komponen induk, komponen indikator progres dimasukkan dengan konstruksi berikut:


     <progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser> 

    Melalui kelas "progreser", pertama-tama, ukuran blok indikator ditetapkan. MaxSamples (jumlah maksimum poin dalam grafik) dari variabel induk SamplesInProgresser ditransfer ke props komponen, dan titik berikutnya (dalam bentuk objek) dari objek orangtua. Variabel Point objek ditransfer ke props Point. Titik induk dihitung dalam fungsi pembaruan, dan menunjukkan peningkatan angka acak. Saya mendapatkan gambar ini:



    Sumber induk App.vue
     <template> <div> <progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser> </div> </template> <script> import Progresser from "./components/Progresser" export default { name: 'app', data(){ return { SamplesInProgresser:400,// -  Point:{value:0},//"" index:0, // -   TimeM:100 //     } }, created: function () { this.update() }, methods:{ update(){ if (this.index < this.SamplesInProgresser) { this.index++; this.Point = {value:(this.TimeM*Math.random() | 0)} this.TimeM *= 1.01 setTimeout(this.update, 0) } } }, components: { Progresser } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; margin-top: 60px; } /*     */ .progresser { width: 300px; height: 80px; } </style> 


  6. Penerapan komponen dalam SPA


    Mencapai titik di mana semuanya berjalan. Jadi, saya memiliki operasi asinkron untuk memuat catatan tentang identitas tertentu dari database. Waktu pelaksanaan operasi asinkron tidak diketahui sebelumnya. Saya akan mengukur waktu eksekusi dengan cara sepele, menggunakan Date () baru. GetTime () sebelum dan setelah operasi, dan saya akan mentransfer perbedaan waktu yang dihasilkan ke komponen. Secara alami, indikator akan dibangun ke dalam blok yang akan muncul pada tahap pemuatan, dan mengaburkan tabel yang memuat data.


     async getCandidatesData(){ ... this.LoadRecords = true //   ,        ... this.SamplesInProgresser = uris.length //      ... for (let item of uris) {// uris  URL    try { const start = new Date().getTime()//   candidate = await this.$store.dispatch('GET_CANDIDATE', item) const stop = new Date().getTime()//   this.Point = {value:(stop-start)}//   Point ... 

    Dalam data komponen induk saya meresepkan sehubungan dengan indikasi beban:


     data (){ return { ... //  LoadRecords:false, SamplesInProgresser:400, Point:{value:0} } 

    Dan di templat:


     <!--   --> <div class="wait_loading" v-show="LoadRecords"> <progresser class="progresser" :maxSamples = "SamplesInProgresser" :Point = "Point" ></progresser> </div> 


Kesimpulan


Seperti yang diperkirakan, tidak ada yang rumit. Hingga titik tertentu, Anda dapat memperlakukan SVG sebagai tag HTML biasa, dengan spesifiknya sendiri. SVG adalah alat yang ampuh yang sekarang akan saya gunakan lebih sering dalam pekerjaan saya untuk visualisasi data


Referensi


Unduh Kode Sumber Indikator
Artikel jalur lgg

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


All Articles