


Flutter adalah rangka kerja UI Google untuk mewujudkan aplikasi mudah alih yang fleksibel dan ekspresif. Ia adalah salah satu rangka kerja yang paling pesat untuk pembangunan aplikasi mudah alih. Fauna, sebaliknya, adalah pangkalan data pelayan tanpa urus niaga, pemaju yang menyokong GraphQL asli. Flutter Fauna adalah perlawanan sempurna yang dibuat oleh syurga. Sekiranya anda ingin membina dan melepaskan aplikasi lengkap yang kaya dengan ciri-ciri dalam masa rekod, Flutter dan Fauna adalah alat yang betul. Dalam artikel ini, kami akan membimbing anda melalui pembinaan aplikasi flutter pertama anda menggunakan backends Fauna dan GraphQL.
Anda boleh mencari kod penuh untuk artikel ini di GitHub.
Objektif pembelajaran
Selepas membaca artikel ini, anda harus tahu bagaimana untuk:
- Sediakan contoh fauna,
- Tulis corak graphql untuk fauna,
- Sediakan klien GraphQL dalam aplikasi Flutter, dan
- Lakukan pertanyaan dan mutasi pada backend GraphQL Fauna.
Fauna vs AWS Amplify vs. Firebase : Apakah masalah yang diselesaikan oleh Fauna? Bagaimana ia berbeza dengan penyelesaian tanpa pelayan lain? Jika anda tidak biasa dengan Fauna dan ingin mengetahui lebih lanjut mengenai perbandingan Fauna dengan penyelesaian lain, saya cadangkan anda membaca artikel ini.
Apa yang kita bina?
Kami akan membina aplikasi mudah alih mudah yang membolehkan pengguna menambah, memadam dan mengemas kini filem kegemaran mereka dan siri TV.
Sediakan Fauna
Pergi ke fauna.com dan buat akaun baru. Selepas log masuk, anda sepatutnya dapat membuat pangkalan data baru.
Namakan pangkalan data anda. Saya menamakan saya flutter_demo. Seterusnya, kita boleh memilih kumpulan rantau. Untuk demo ini, kami akan memilih Klasik. Fauna adalah pangkalan data tanpa pelayan di seluruh dunia. Ia adalah satu-satunya pangkalan data yang menyokong akses membaca dan menulis latensi rendah dari mana-mana sahaja. Fikirkannya sebagai CDN (rangkaian pengedaran kandungan), tetapi ia adalah untuk pangkalan data anda. Untuk mengetahui lebih lanjut mengenai kumpulan rantau ini, ikuti panduan ini.
Menjana kunci pentadbir
Sebaik sahaja pangkalan data dibuat, pergi ke tab Keselamatan. Klik butang kunci baru dan buat kekunci baru untuk pangkalan data anda. Sila simpan kunci ini dengan betul kerana kami memerlukannya untuk operasi GraphQL.
Kami akan membuat kunci pentadbir untuk pangkalan data kami. Kunci dengan peranan pentadbir digunakan untuk menguruskan pangkalan data yang berkaitan, termasuk penyedia akses pangkalan data, subdatabase, dokumen, fungsi, indeks, kunci, token, dan peranan yang ditentukan oleh pengguna. Anda boleh mengetahui lebih lanjut mengenai pelbagai kunci keselamatan Fauna dan peranan akses dalam pautan di bawah.
Menulis corak GraphQL
Kami akan membina aplikasi mudah yang membolehkan pengguna menambah, mengemas kini dan memadam watak TV kegemaran mereka.
Buat projek Flutter baru
Mari buat projek Flutter baru dengan menjalankan arahan berikut.
<code>flutter create my_app</code>
Dalam direktori projek, kami akan membuat fail baru bernama Graphql/schema.graphql.
Dalam fail skema, kami akan menentukan struktur koleksi. Koleksi di fauna adalah serupa dengan jadual dalam SQL. Kami hanya memerlukan satu set sekarang. Kami namakan wataknya.
<code>### schema.graphql type Character { name: String! description: String! picture: String } type Query { listAllCharacters: [Character] }</code>
Seperti yang ditunjukkan di atas, kami menentukan jenis yang dipanggil watak yang mempunyai pelbagai sifat (iaitu, nama, keterangan, gambar, dll.). Hartanah boleh dianggap sebagai lajur pangkalan data SQL atau pasangan nilai utama pangkalan data NoSQL. Kami juga menentukan pertanyaan. Pertanyaan ini mengembalikan senarai peranan.
Sekarang mari kita kembali ke papan pemuka Fauna. Klik GraphQL dan klik Mod Import untuk memuat naik mod kami ke Fauna.
Selepas import selesai, kami akan melihat Fauna menghasilkan pertanyaan dan mutasi GraphQL.
Tidak suka generasi graphql automatik? Ingin mempunyai kawalan yang lebih baik terhadap logik perniagaan anda? Dalam kes ini, Fauna membolehkan anda menentukan parser GraphQL tersuai. Untuk mengetahui lebih lanjut, klik pautan ini.
Menyediakan pelanggan Graphql dalam Aplikasi Flutter
Mari buka fail pubspec.yaml dan tambahkan kebergantungan yang diperlukan.
<code>... dependencies: graphql_flutter: ^4.0.0-beta hive: ^1.3.0 flutter: sdk: flutter ...</code>
Kami telah menambah dua kebergantungan di sini. Graphql_flutter adalah Perpustakaan Pelanggan Graphql Flutter. Ia mengintegrasikan semua ciri moden klien GraphQL ke dalam satu pakej yang mudah digunakan. Kami juga menambah pakej sarang sebagai kebergantungan kami. HIVE adalah pangkalan data nilai utama yang ditulis dalam Pure Dart untuk penyimpanan tempatan. Kami menggunakan sarang untuk cache pertanyaan graphql kami.
Seterusnya, kami akan membuat fail baru lib/client_provider.dart. Kami akan membuat kelas penyedia dalam fail yang akan mengandungi konfigurasi Fauna kami.
Untuk menyambung ke API GraphQL Fauna, kita perlu membuat GraphQLClient terlebih dahulu. GraphqlClient memerlukan cache dan pautan untuk dimulakan. Mari lihat kod di bawah.
<code>// lib/client_provider.dart import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:flutter/material.dart'; ValueNotifier<graphqlclient> clientFor({ @required String uri, String subscriptionUri, }) { final HttpLink httpLink = HttpLink( uri, ); final AuthLink authLink = AuthLink( getToken: () async => 'Bearer fnAEPAjy8QACRJssawcwuywad2DbB6ssrsgZ2-2', ); Link link = authLink.concat(httpLink); return ValueNotifier<graphqlclient> ( GraphQLClient( cache: GraphQLCache(store: HiveStore()), link: link, ), ); }</graphqlclient></graphqlclient></code>
Dalam kod di atas, kami mencipta Valuenotifier untuk membungkus GraphqlClient. Perhatikan bahawa kami mengkonfigurasi authLink pada baris 13-15 (diserlahkan). Pada baris 14, kami menambah kunci pentadbir dari Fauna sebagai sebahagian daripada token. Di sini, saya mengikat kunci admin. Walau bagaimanapun, dalam aplikasi pengeluaran, kita mesti mengelakkan kekunci keselamatan fauna.
Terdapat beberapa cara untuk menyimpan kekunci dalam aplikasi berkilauan. Sila periksa blog ini untuk rujukan.
Kami mahu dapat memanggil pertanyaan dan mutasi dari mana -mana widget dalam aplikasi. Untuk melakukan ini, kita perlu membungkus widget kita menggunakan widget GraphQLProvider.
<code>// lib/client_provider.dart .... /// 使用`graphql_flutter`客户端包装根应用程序。 /// 我们使用缓存进行所有状态管理。 class ClientProvider extends StatelessWidget { ClientProvider({ @required this.child, @required String uri, }) : client = clientFor( uri: uri, ); final Widget child; final ValueNotifier<graphqlclient> client; @override Widget build(BuildContext context) { return GraphQLProvider( client: client, child: child, ); } }</graphqlclient></code>
Seterusnya, kami pergi ke fail main.dart dan bungkus widget utama kami dengan widget ClientProvider. Mari lihat kod di bawah.
<code>// lib/main.dart ... void main() async { await initHiveForFlutter(); runApp(MyApp()); } final graphqlEndpoint = 'https://graphql.fauna.com/graphql'; class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ClientProvider( uri: graphqlEndpoint, child: MaterialApp( title: 'My Character App', debugShowCheckedModeBanner: false, initialRoute: '/', routes: { '/': (_) => AllCharacters(), '/new': (_) => NewCharacter(), } ), ); } }</code>
Pada ketika ini, semua widget hiliran kami akan dapat menjalankan pertanyaan dan fungsi mutasi dan boleh berinteraksi dengan API GraphQL.
Halaman permohonan
Permohonan demo mestilah mudah dan mudah difahami. Mari kita teruskan dan buat widget senarai mudah yang akan memaparkan senarai semua peranan. Mari buat fail lib/skrin/list characters.dart baru. Dalam fail ini, kami akan menulis widget baru yang dipanggil AllCharacters.
<code>// lib/screens/character-list.dart.dart class AllCharacters extends StatelessWidget { const AllCharacters({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( slivers: [ SliverAppBar( pinned: true, snap: false, floating: true, expandedHeight: 160.0, title: Text( 'Characters', style: TextStyle( fontWeight: FontWeight.w400, fontSize: 36, ), ), actions:<widget> [ IconButton( padding: EdgeInsets.all(5), icon: const Icon(Icons.add_circle), tooltip: 'Add new entry', onPressed: () { Navigator.pushNamed(context, '/new'); }, ), ], ), SliverList( delegate: SliverChildListDelegate([ Column( children: [ for (var i = 0; i _CharacterTileeState(); } class _CharacterTileState extends State<charactertile> { @override Widget build(BuildContext context) { return Container( child: Text("Character Tile"), ); } }</charactertile></widget></code>
Seperti yang ditunjukkan dalam kod di atas, [baris 37] kami mempunyai gelung yang mengisi senarai dengan beberapa data palsu. Akhirnya, kami akan melakukan pertanyaan GraphQL pada backend Fauna dan dapatkan semua peranan dari pangkalan data. Sebelum kita melakukan ini, mari kita cuba menjalankan permohonan kita. Kami boleh menjalankan aplikasi kami menggunakan arahan berikut
<code>flutter run</code>
Pada ketika ini, kita harus dapat melihat skrin berikut.
Laksanakan pertanyaan dan mutasi
Sekarang kita mempunyai beberapa widget asas yang kita boleh terus menyambung ke pertanyaan GraphQL. Kami mahu mendapatkan semua peranan dari pangkalan data dan bukannya rentetan keras dan melihatnya dalam widget AllCharacters.
Mari kembali ke taman permainan GraphQL Fauna. Perhatikan bahawa kami boleh menjalankan pertanyaan berikut untuk menyenaraikan semua peranan.
<code>query ListAllCharacters { listAllCharacters(_size: 100) { data { _id name description picture } after } }</code>
Untuk melakukan pertanyaan ini dari widget kami, kami perlu membuat beberapa perubahan kepadanya.
<code>import 'package:flutter/material.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:todo_app/screens/Character-tile.dart'; String readCharacters = ";";"; query ListAllCharacters { listAllCharacters(_size: 100) { data { _id name description picture } after } } ";";";; class AllCharacters extends StatelessWidget { const AllCharacters({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( slivers: [ SliverAppBar( pinned: true, snap: false, floating: true, expandedHeight: 160.0, title: Text( 'Characters', style: TextStyle( fontWeight: FontWeight.w400, fontSize: 36, ), ), actions:<widget> [ IconButton( padding: EdgeInsets.all(5), icon: const Icon(Icons.add_circle), tooltip: 'Add new entry', onPressed: () { Navigator.pushNamed(context, '/new'); }, ), ], ), SliverList( delegate: SliverChildListDelegate([ Query(options: QueryOptions( document: gql(readCharacters), // 我们要执行的graphql查询pollInterval: Duration(seconds: 120), // 重新获取间隔), builder: (QueryResult result, { VoidCallback refetch, FetchMore fetchMore }) { if (result.isLoading) { return Text('Loading'); } return Column( children: [ for (var item in result.data\['listAllCharacters'\]['data']) CharacterTile(Character: item, refetch: refetch), ], ); }) ]) ) ], ), ); } }</widget></code>
Pertama, kami menentukan rentetan pertanyaan untuk mendapatkan semua peranan dari pangkalan data [baris 5 hingga 17]. Kami membungkus widget senarai menggunakan widget pertanyaan di flutter_graphql.
Jangan ragu untuk melihat dokumentasi rasmi Perpustakaan Flutter_Graphql.
Dalam parameter pilihan pertanyaan, kami menyediakan rentetan pertanyaan GraphQL itu sendiri. Kita boleh lulus sebarang nombor titik terapung untuk parameter pollinterval. Selang Poll Menentukan berapa lama kita mahu memulihkan data dari backend. Widget juga mempunyai fungsi pembina standard. Kami boleh menggunakan fungsi pembina untuk lulus hasil pertanyaan, dapatkan semula fungsi panggil balik, dan dapatkan lebih banyak fungsi panggil balik ke dalam pokok widget.
Seterusnya, saya akan mengemas kini widget Watak untuk memaparkan data aksara pada skrin.
<code>// lib/screens/character-tile.dart ... class CharacterTile extends StatelessWidget { final Character; final VoidCallback refetch; final VoidCallback updateParent; const CharacterTile({ Key key, @required this.Character, @required this.refetch, this.updateParent, }) : super(key: key); @override Widget build(BuildContext context) { return InkWell( onTap: () { }, child: Padding( padding: const EdgeInsets.all(10), child: Row( children: [ Container( height: 90, width: 90, decoration: BoxDecoration( color: Colors.amber, borderRadius: BorderRadius.circular(15), image: DecorationImage( fit: BoxFit.cover, image: NetworkImage(Character['picture']) ) ), ), SizedBox(width: 10), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( Character['name'], style: TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, ), ), SizedBox(height: 5), Text( Character['description'], style: TextStyle( color: Colors.black87, ), maxLines: 2, ), ], ) ) ], ), ), ); } }</code>
Tambah data baru
Kami boleh menambah peranan baru ke pangkalan data kami dengan menjalankan mutasi berikut.
<code>mutation CreateNewCharacter($data: CharacterInput!) { createCharacter(data: $data) { _id name description picture } }</code>
Untuk menjalankan mutasi ini dari widget kami, kami perlu menggunakan widget mutasi dari perpustakaan Flutter_Graphql. Mari buat widget baru dengan bentuk mudah untuk pengguna berinteraksi dan memasukkan data. Selepas menyerahkan borang, mutasi createcharacter akan dipanggil.
<code>// lib/screens/new.dart ... String addCharacter = ";";"; mutation CreateNewCharacter(\$data: CharacterInput!) { createCharacter(data: \$data) { _id name description picture } } ";";";; class NewCharacter extends StatelessWidget { const NewCharacter({Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add New Character'), ), body: AddCharacterForm() ); } } class AddCharacterForm extends StatefulWidget { AddCharacterForm({Key key}) : super(key: key); @override _AddCharacterFormState createState() => _AddCharacterFormState(); } class _AddCharacterFormState extends State<addcharacterform> { String name; String description; String imgUrl; @override Widget build(BuildContext context) { return Form( child: Padding( padding: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( decoration: const InputDecoration( icon: Icon(Icons.person), labelText: 'Name *', ), onChanged: (text) { name = text; }, ), TextField( decoration: const InputDecoration( icon: Icon(Icons.post_add), labelText: 'Description', ), minLines: 4, maxLines: 4, onChanged: (text) { description = text; }, ), TextField( decoration: const InputDecoration( icon: Icon(Icons.image), labelText: 'Image Url', ), onChanged: (text) { imgUrl = text; }, ), SizedBox(height: 20), Mutation( options: MutationOptions( document: gql(addCharacter), onCompleted: (dynamic resultData) { print(resultData); name = ''; description = ''; imgUrl = ''; Navigator.of(context).push( MaterialPageRoute(builder: (context) => AllCharacters()) ); }, ), builder: ( RunMutation runMutation, QueryResult result, ) { return Center( child: ElevatedButton( child: const Text('Submit'), onPressed: () { runMutation({ 'data': { ";picture";: imgUrl, ";name";: name, ";description";: description, } }); }, ), ); } ) ], ), ), ); } }</addcharacterform></code>
Seperti yang dapat dilihat dari kod di atas, widget mutasi berfungsi dengan sangat sama dengan widget pertanyaan. Di samping itu, widget mutasi memberikan kita fungsi oncomplete. Fungsi ini mengembalikan hasil kemas kini dalam pangkalan data selepas mutasi selesai.
Padam data
Kita boleh memadam peranan dari pangkalan data dengan menjalankan mutasi DeleteCharacter. Kita boleh menambah mutan ini ke watak kita dan mencetuskannya apabila butang ditekan.
<code>// lib/screens/character-tile.dart ... String deleteCharacter = ";";"; mutation DeleteCharacter(\$id: ID!) { deleteCharacter(id: \$id) { _id name } } ";";";; class CharacterTile extends StatelessWidget { final Character; final VoidCallback refetch; final VoidCallback updateParent; const CharacterTile({ Key key, @required this.Character, @required this.refetch, this.updateParent, }) : super(key: key); @override Widget build(BuildContext context) { return InkWell( onTap: () { showModalBottomSheet( context: context, builder: (BuildContext context) { print(Character['picture']); return Mutation( options: MutationOptions( document: gql(deleteCharacter), onCompleted: (dynamic resultData) { print(resultData); this.refetch(); }, ), builder: ( RunMutation runMutation, QueryResult result, ) { return Container( height: 400, padding: EdgeInsets.all(30), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children:<widget> [ Text(Character['description']), ElevatedButton( child: Text('Delete Character'), onPressed: () { runMutation({ 'id': Character['_id'], }); Navigator.pop(context); }, ), ], ), ), ); } ); } ); }, child: Padding( padding: const EdgeInsets.all(10), child: Row( children: [ Container( height: 90, width: 90, decoration: BoxDecoration( color: Colors.amber, borderRadius: BorderRadius.circular(15), image: DecorationImage( fit: BoxFit.cover, image: NetworkImage(Character['picture']) ) ), ), SizedBox(width: 10), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( Character['name'], style: TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, ), ), SizedBox(height: 5), Text( Character['description'], style: TextStyle( color: Colors.black87, ), maxLines: 2, ), ], ) ) ], ), ), ); } }</widget></code>
Edit data
Data pengeditan adalah sama seperti menambah dan memadam. Ia hanya mutasi lain dalam API GraphQL. Kami boleh membuat widget bentuk role edit yang serupa dengan widget bentuk peranan baru. Satu -satunya perbezaan ialah penyuntingan borang akan menjalankan mutasi updatecharacter. Untuk mengedit, saya mencipta widget baru lib/skrin/edit.dart. Berikut adalah kod untuk widget ini.
<code>// lib/screens/edit.dart String editCharacter = """ mutation EditCharacter(\$name: String!, \$id: ID!, \$description: String!, \$picture: String!) { updateCharacter(data: { name: \$name description: \$description picture: \$picture }, id: \$id) { _id name description picture } } """; class EditCharacter extends StatelessWidget { final Character; const EditCharacter({Key key, this.Character}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Edit Character'), ), body: EditFormBody(Character: this.Character), ); } } class EditFormBody extends StatefulWidget { final Character; EditFormBody({Key key, this.Character}) : super(key: key); @override _EditFormBodyState createState() => _EditFormBodyState(); } class _EditFormBodyState extends State<editformbody> { String name; String description; String picture; @override Widget build(BuildContext context) { return Container( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( initialValue: widget.Character['name'], decoration: const InputDecoration( icon: Icon(Icons.person), labelText: 'Name *', ), onChanged: (text) { name = text; } ), TextFormField( initialValue: widget.Character['description'], decoration: const InputDecoration( icon: Icon(Icons.person), labelText: 'Description', ), minLines: 4, maxLines: 4, onChanged: (text) { description = text; } ), TextFormField( initialValue: widget.Character['picture'], decoration: const InputDecoration( icon: Icon(Icons.image), labelText: 'Image Url', ), onChanged: (text) { picture = text; }, ), SizedBox(height: 20), Mutation( options: MutationOptions( document: gql(editCharacter), onCompleted: (dynamic resultData) { print(resultData); Navigator.of(context).push( MaterialPageRoute(builder: (context) => AllCharacters()) ); }, ), builder: ( RunMutation runMutation, QueryResult result, ) { print(result); return Center( child: ElevatedButton( child: const Text('Submit'), onPressed: () { runMutation({ 'id': widget.Character['_id'], 'name': name != null ? name : widget.Character['name'], 'description': description != null ? description : widget.Character['description'], 'picture': picture != null ? picture : widget.Character['picture'], }); }, ), ); } ), ] ) ), ); } }</editformbody></code>
Anda boleh melihat kod penuh artikel ini seperti berikut.
Ada soalan mengenai fauna atau berkibar? Anda boleh menghubungi saya di Twitter @Haqueshadid
Github ### Langkah seterusnya
Tujuan utama artikel ini adalah untuk membuat anda bermula dengan Flutter dan Fauna. Kami hanya menyentuh permukaan di sini. Ekosistem Fauna menyediakan aplikasi mudah alih anda dengan backend yang lengkap, automatik, pemaju yang lengkap sebagai perkhidmatan. Sekiranya matlamat anda adalah untuk melepaskan aplikasi mudah alih rentas platform yang boleh digunakan untuk pengeluaran dalam masa rekod, cuba Fauna dan Flutter .
Saya sangat mengesyorkan anda menyemak laman web dokumentasi rasmi Fauna. Jika anda berminat untuk mempelajari lebih lanjut mengenai klien GraphQL Dart/Flutter, lihat repositori GitHub rasmi Graphql_flutter.
Saya mengucapkan selamat datang kepada anda, sampai jumpa lagi.
Atas ialah kandungan terperinci Cara membina aplikasi mudah alih penuh dengan Flutter, Fauna, dan Graphql. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

CSS Grid adalah alat yang berkuasa untuk mewujudkan susun atur web yang rumit dan responsif. Ia memudahkan reka bentuk, meningkatkan kebolehcapaian, dan menawarkan lebih banyak kawalan daripada kaedah yang lebih lama.

Artikel membincangkan CSS Flexbox, kaedah susun atur untuk penjajaran yang cekap dan pengedaran ruang dalam reka bentuk responsif. Ia menerangkan penggunaan Flexbox, membandingkannya dengan grid CSS, dan butiran sokongan penyemak imbas.

Artikel ini membincangkan teknik untuk membuat laman web responsif menggunakan CSS, termasuk tag Meta Viewport, grid fleksibel, media cecair, pertanyaan media, dan unit relatif. Ia juga meliputi menggunakan grid CSS dan Flexbox bersama -sama dan mengesyorkan rangka kerja CSS

Artikel ini membincangkan harta saiz kotak CSS, yang mengawal bagaimana dimensi elemen dikira. Ia menerangkan nilai seperti kotak kandungan, kotak sempadan, dan kotak padding, dan kesannya terhadap reka bentuk susun atur dan penjajaran bentuk.

Artikel membincangkan membuat animasi menggunakan CSS, sifat utama, dan menggabungkan dengan JavaScript. Isu utama adalah keserasian penyemak imbas.

Artikel membincangkan menggunakan CSS untuk transformasi 3D, sifat utama, keserasian penyemak imbas, dan pertimbangan prestasi untuk projek web. (Kira -kira aksara: 159)

Artikel ini membincangkan menggunakan kecerunan CSS (linear, radial, mengulangi) untuk meningkatkan visual laman web, menambah kedalaman, fokus, dan estetika moden.

Artikel membincangkan unsur-unsur pseudo dalam CSS, penggunaannya dalam meningkatkan gaya HTML, dan perbezaan dari kelas pseudo. Menyediakan contoh praktikal.


Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

SublimeText3 versi Inggeris
Disyorkan: Versi Win, menyokong gesaan kod!

Pelayar Peperiksaan Selamat
Pelayar Peperiksaan Selamat ialah persekitaran pelayar selamat untuk mengambil peperiksaan dalam talian dengan selamat. Perisian ini menukar mana-mana komputer menjadi stesen kerja yang selamat. Ia mengawal akses kepada mana-mana utiliti dan menghalang pelajar daripada menggunakan sumber yang tidak dibenarkan.

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Muat turun versi mac editor Atom
Editor sumber terbuka yang paling popular

VSCode Windows 64-bit Muat Turun
Editor IDE percuma dan berkuasa yang dilancarkan oleh Microsoft
