首頁 > web前端 > H5教程 > 主體

使用分層畫布來優化HTML5渲染的教學_html5教學技巧

WBOY
發布: 2016-05-16 15:46:40
原創
1747 人瀏覽過

はじめに

通常、2D ゲームをプレイするとき、または HTML5 キャンバスをレンダリングするときは、複数のレイヤーを使用して複合シーンを構築するための最適化を実行する必要があります。 OpenGL や WebGL などの低レベルのレンダリングでは、フレームごとにシーンをクリーニングしてペイントすることによってレンダリングが実行されます。レンダリングを実装した後は、レンダリング量を減らすためにゲームを最適化する必要があり、コストは状況によって異なります。キャンバスは DOM 要素であるため、最適化の方法として複数のキャンバスを階層化できます。
一般的に使用される略語

  • CSS: カスケード スタイル シート
    DOM: ドキュメント オブジェクト モデル
    HTML: ハイパーテキスト マークアップ言語

この記事では、キャンバスをレイヤー化する理論的根拠を説明します。レイヤードキャンバスを実装するための DOM 設定を理解します。階層化を使用して最適化するには、さまざまな実践が必要です。この記事では、階層化アプローチを拡張するいくつかの最適化戦略の概念と手法についても説明します。

この記事で使用されている例のソース コードをダウンロードできます。
最適化戦略を選択します

最適な最適化戦略を選択するのは難しい場合があります。レイヤードシーンを選択するときは、シーンがどのように構成されているかを考慮する必要があります。大画面上で静止したオブジェクトをレンダリングするには、多くの場合、複数のコンポーネントを再利用する必要があり、それらは研究の優れた候補です。視差やアニメーション化されたエンティティなどの効果は、多くの場合、さまざまな画面スペースを大量に必要とします。最適な最適化戦略を検討するときは、これらの状況を認識しておくことをお勧めします。キャンバス レイヤの最適化にはいくつかの異なるテクニックが必要ですが、これらのテクニックを正しく適用すると、多くの場合、パフォーマンスが大幅に向上します。
レイヤーを設定

階層化アプローチを使用する場合、最初のステップは DOM 上にキャンバスを設定することです。通常、これはキャンバス要素を定義して DOM に配置するだけで簡単ですが、キャンバス レイヤーには追加のスタイル設定が必要になる場合があります。 CSS を使用するときにキャンバス レイヤを正常に実装するには、次の 2 つの要件があります。

各キャンバス要素はビューポート内の同じ位置に共存する必要があります。
各キャンバスは別のキャンバスの下に表示される必要があります。

図 1 は、レイヤー設定の背後にある一般的なオーバーレイの概念を示しています。
図 1. レイヤーの例
201558165954791.gif (288×173)
レイヤーを設定する手順は次のとおりです:

  • DOM に Canvas 要素を追加します。
    レイヤー化をサポートするために、キャンバス要素の配置スタイルを追加します。
    キャンバス要素をスタイル設定して、透明な背景を生成します。

キャンバスのオーバーラップスタックを設定します

CSS でオーバーレイ スタックを作成するには、少量のスタイル設定が必要な場合があります。 HTML と CSS を使用してオーバーラップする方法は数多くあります。この記事の例では、

タグを使用してキャンバスを含めます。
タグは、子 HTML5 キャンバス要素にスタイルを適用する一意の ID を指定します (リスト 1 を参照)。
リスト 1. キャンバスの配置スタイル

CSS コードコンテンツをクリップボードにコピーします
  1. #viewport {
  2. /**
  3. * キャンバス要素
  4. となるように相対的に配置します。
  5. * その内側は 親
  6. との相対になります。
  7.      */
  8. 位置: 相対;
  9. }
  10. #viewport Canvas {
  11. /**
  12. * 絶対位置は、 を可能にするキャンバスを提供します。
  13. * 相互に重ねて重ねていきます
  14. * Z インデックスを必ず覚えておいてください。
  15.      */
  16. 位置: 絶対; }
  17. Container
    は、すべての子 Canvas 要素を絶対配置を使用するようにスタイル設定することで、重複要件を満たします。 #viewport で相対配置を使用することを選択すると、将来に備えて、子スタイルに適用される絶対レイアウト スタイルが #viewport コンテナに対して相対的になります。
    これらの HTML5 キャンバス要素の順序も重要です。順序は、要素が DOM 上に表示される順序によって、またはキャンバスが表示される順序で z-index スタイルをスタイル設定することによって管理できます。常にそうであるとは限りませんが、他のスタイルがレンダリングに影響を与える可能性があります。追加のスタイル (あらゆる種類の CSS 変換など) を導入するときは注意してください。
透明な背景

可視性の重複を使用して、レイヤー技術の 2 番目のスタイル要件を達成します。この例では、リスト 2 に示すように、このオプションを使用して DOM 要素の背景色を設定します。

リスト 2. 透明な背景を設定するためのスタイルシートのルール




XML/HTML コード

コンテンツをクリップボードにコピー
キャンバス {
  1. /**  
  2. *
  3. を通じて他のキャンバスをレンダリングできるように 透明 に設定します。
  4.      */
  5. 背景色: 透明;
  6. }
  7. 背景が透明になるようにキャンバスのスタイルを設定します。これにより、重なったキャンバスが表示されるという 2 番目の要件が満たされます。レイヤー化のニーズを満たすようにマークアップとスタイルを構造化したので、レイヤー化されたシーンをセットアップできます。
    レイヤリングに関する考慮事項

    最適化戦略を選択するときは、その戦略を使用する際のすべてのトレードオフを認識する必要があります。 HTML5 キャンバス シーンの階層化は、実行速度の利点を得るために使用される実行時のメモリに重点を置いた戦略です。ページのブラウザにさらに重みを追加して、フレーム レートを高速化できます。一般に、キャンバスはブラウザ上のグラフィックス プレーンとみなされ、グラフィックス API が含まれます。

    Google Chrome 19 でテストし、ブラウザのタブのメモリ使用量を記録することで、メモリ使用量の明確な傾向を確認できます。このテストでは、(前のセクションで説明したように) すでにスタイル設定されている

    を使用し、単一の色で塗りつぶされた
    上に配置される Canvas 要素を生成します。キャンバスのサイズは 1600 x 900 ピクセルに設定され、データは Chrome1 のタスク マネージャー ユーティリティから収集されました。表 1 に一例を示します。

    Google Chrome のタスク マネージャーでは、ページで使用されているメモリ (RAM とも呼ばれます) の量を確認できます。 Chrome は、GPU メモリ、または GPU によって使用されるメモリも提供します。これは、ジオメトリ、テクスチャ、またはコンピューターがキャンバス データを画面にプッシュするために必要となるあらゆる形式のキャッシュ データなどの一般的な情報です。メモリが少ないほど、コンピュータにかかる重量が減ります。根拠となる明確な数値はまだありませんが、プログラムが限界を超えてメモリを過剰に使用していないことを確認するために、常にテストする必要があります。メモリの使用量が多すぎると、メモリ リソース不足によりブラウザまたはページがクラッシュします。 GPU 処理は野心的なプログラミングの追求ですが、この記事の範囲を超えています。まずは OpenGL を学ぶか、Chrome のドキュメントを確認することから始めてください (「参考文献」を参照)。
    表 1. キャンバス層のメモリオーバーヘッド
    201558170025301.jpg (651×315)

    表 1 では、より多くの HTML5 キャンバス要素がページに導入され、使用されると、より多くのメモリが使用されます。一般的なメモリにも線形相関がありますが、層が追加されるたびにメモリの増加は大幅に減少します。このテストでは、これらのレイヤーのパフォーマンスへの影響は詳しく説明されていませんが、キャンバスが GPU メモリに重大な影響を与える可能性があることが示されています。プラットフォームの制限によってアプリケーションが実行できなくなることがないように、ターゲット プラットフォームでストレス テストを必ず実行してください。

    階層化ソリューションの単一キャンバスのレンダリング サイクルを変更する場合は、メモリ オーバーヘッドに関するパフォーマンスの向上を考慮してください。メモリのコストは高くなりますが、この技術は各フレームで変更されるピクセルの数を減らすことで効果を発揮します。

    次のセクションでは、レイヤーを使用してシーンを整理する方法について説明します。
    シーンのレイヤー化: ゲーム

    このセクションでは、スクロール プラットフォームのランナー スタイル ゲームでの視差効果の単一キャンバス実装をリファクタリングすることにより、多層ソリューションを見ていきます。図 2 は、雲、丘、地面、背景、およびいくつかのインタラクティブなエンティティを含むゲーム ビューの構成を示しています。
    図 2. 合成ゲームビュー
    201558170059746.jpg (300×169)

    ゲームでは、雲、丘、地面、背景はすべて異なる速度で動きます。基本的に、背景の奥にある要素は手前の要素よりも遅く移動するため、視差効果が生じます。状況をさらに複雑にしているのは、背景の動きが 0.5 秒ごとにのみ再レンダリングされるほど遅いことです。

    背景は画像であり常に変化するため、通常はすべてのフレームをクリアして画面を再レンダリングするのが良い解決策となります。この場合、背景は 1 秒あたり 2 回しか変化しないため、各フレームを再レンダリングする必要はありません。

    現在、ワークスペースが定義されているので、シーンのどの部分を同じレイヤー上に置くかを決定できます。レイヤーを整理したら、レイヤー化のためのさまざまなレンダリング戦略を検討します。まず、リスト 3 に示すように、単一のキャンバスを使用してこのソリューションを実装する方法を検討する必要があります。
    リスト 3. 単一キャンバスのレンダリング ループの疑似コード

    XML/HTML コードコンテンツをクリップボードにコピー
    1. /**  
    2. * レンダリング呼び出し
    3. *
    4. * @param {CanvasRenderingContext2D} context Canvas context
    5.  */
    6. 関数 renderLoop(context)
    7. {
    8. context.clearRect(0, 0, width, height);
    9. 背景.render(コンテキスト)
    10. グラウンド .render(context);
    11. ヒルズ.レンダー(コンテキスト)
    12. クラウド.レンダー(コンテキスト)
    13. player.render(context);
    14. }
    15. リスト 3 のコードと同様に、このソリューションには、ゲーム ループ呼び出しごと、または更新間隔ごとに呼び出されるレンダリング関数が含まれます。この場合、レンダリングはメイン ループ呼び出しと各要素の位置を更新する更新呼び出しから抽象化されます。
    「レンダリングをクリア」ソリューションに従って、render はクリア コンテキストを呼び出し、画面のそれぞれのレンダリング関数上のエンティティを呼び出すことによってそれを追跡します。リスト 3 では、プログラムによるパスに従ってキャンバスに要素を配置します。このソリューションは画面上でエンティティをレンダリングする場合には効果的ですが、使用されるすべてのレンダリング方法については説明しておらず、いかなる形式のレンダリング最適化もサポートしていません。

    エンティティのレンダリング方法をより適切に指定するには、2 種類のエンティティ オブジェクトを使用する必要があります。リスト 4 は、使用して調整する 2 つのエンティティを示しています。

    リスト 4. レンダリング可能なエンティティの擬似コード




    XML/HTML コード

    コンテンツをクリップボードにコピー
    var
    1. エンティティ = 関数() { /**  
    2. 初期化 および その他のメソッド
    3. **/
    4. /**  
    5. * エンティティを描画するためのレンダリング呼び出し
    6. *
    7. * @param {CanvasRenderingContext2D} context
    8.       */
    9. this.render
    10. = 関数(コンテキスト) { context.drawImage(this.image, this.x, this.y);
    11. }
    12. };


    XML/HTML コード

    コンテンツをクリップボードにコピー
    1. var PanningEntity = 函數())     /**  
    2.      初始化及其他方法   
    3.      **/   
    4.     
    5.     /**  
    6.       * 渲染呼叫以繪製平移實體  
    7.       *   
    8.       * @param {CanvasRenderingContext2D} 上下文  
    9.      */   
    10.     
    11. this.render = 函數(情境)         context.drawImage(   
    12.             此.image,   
    13.             this.x - this.width,  
    14.             this.y - this.height);   
    15.         context.drawImage(   
    16.             此.image,   
    17.             this.x,  
    18.             此.y);   
    19.         context.drawImage(   
    20.             此.image,   
    21.             this.x   this.width,   
    22.             this.y   this.height);   
    23.     }   
    24. };  
    25. 清單 4中的物件儲存實體的圖像、x、y、寬度和高度的實例變數。這些物件遵循 JavaScript 語法,但為了簡潔起見,僅提供了目標物件的不完整的偽代碼。目前,渲染演算法非常貪婪地在畫布上渲染出它們的圖像,完全不考慮遊戲循環的其他任何要求。

      為了提高效能,需要重點注意的是,panning渲染呼叫輸出了一個比所需影像更大的影像。本文忽略這個特定的最佳化,但是,如果使用的空間比您的圖像提供的空間小,那麼請確保只渲染必要的補丁。
      確定分層

      現在您知道如何使用單一畫布實作該範例,讓我們看看有什麼辦法可以完善這種類型的場景,並加快渲染循環。若要使用分層技術,則必須透過找出實體的渲染重疊,識別分層所需的 HTML5 畫布元素。
      重繪區域

      為了確定是否存在重疊,要考慮一些被稱為重繪區域的不可見區域。重繪區域是在繪製實體的影像時需要畫布清除的區域。重繪區域對於渲染分析很重要,因為它們使您能夠找到完善渲染場景的最佳化技術,如圖 3所示。
      圖 3. 合成遊戲視圖與重繪區域
      201558170130006.jpg (300×169)

      為了視覺化圖 3中的效果,在場景中的每個實體都有一個表示重繪區域的重疊,它跨越了視區寬度和實體的影像高度。場景可分為三組:背景、前景、互動。場景中的重繪區域有一個彩色的重疊,以區分不同的區域:

      •     背景 – 黑色
            雲 – 紅色
            小山 – 綠色
            地面 – 藍色 

        對於除了球和障礙物以外的所有重疊,重繪區域都會橫跨視區寬度。這些實體的圖像幾乎填滿整個螢幕。由於它們的平移要求,它們將渲染整個視區寬度,如圖 4所示。預計球和障礙物會穿過該視區,並且可能擁有透過實體位置定義的各自的區域。如果您刪除渲染到場景的影像,只留下重繪區域,您可以輕鬆看到單獨的圖層。
      • 圖 4.重繪區域



      初始層是顯而易見的,因為您可以注意到互相重疊的各個區域。由於球和障礙物區域覆蓋了小山和地面,所以可將這些實體分組為一層,該層稱為交互層。根據遊戲實體的渲染順序,交互層是頂層。 201558170200050.jpg (300×169)

      找到附加層的另一種方法是收集沒有重疊的所有區域。佔據視區的紅色、綠色和藍色區域並沒有重疊,它們組成了第二層——前景。雲和互動實體的區域沒有重疊,但因為球有可能跳躍到紅色區域,所以您應該考慮將該實體作為一個單獨的層。

      對於黑色區域,可以輕易推斷出,背景實體將會組成最後一層。填滿整個視區的任何區域(如背景實體)都應視為填滿整個圖層中的該區域,雖然這對本場景並不適用。在定義了我們的三個層次之後,我們就可以開始將這層分配給畫布,如圖 5所示。

      圖 5. 分層的遊戲視圖



      現在已經為每個分組的實體定義了圖層,現在就可以開始最佳化畫布清除。此優化的目標是為了節省處理時間,可以透過減少每一步渲染的螢幕上的固定物數量來實現。需要重點注意的是,使用不同的策略可能會使影像獲得更好的最佳化。下一節將探討各種實體或層的最佳化方法。 201558170232257.jpg (228×125)渲染最佳化

      最佳化實體是分層策略的核心。將實體分層,使得渲染策略可以被採用。通常,最佳化技術會試圖消除開銷。如表 1所述,由於引入了層,您已經增加了記憶體開銷。這裡討論的最佳化技術將減少處理器為了加快遊戲而必須執行的大量工作。我們的目標是尋找一種減少要渲染的空間量的方法,並盡可能刪除每個步驟中出現的渲染和清除呼叫。
      單一實體清除

      第一個最佳化方法針對的是清除空間,透過只清除組成該實體的螢幕子集來加快處理。首先減少與區域的各實體周圍的透明像素重疊的重繪區域量。使用此技術的包括相對較小的實體,它們填充了視區的小區域。

      最初のターゲットはボールと障害物です。単一エンティティのクリア手法には、エンティティを新しい位置にレンダリングする前に、前のフレームでエンティティがレンダリングされた位置をクリアすることが含まれます。各エンティティのレンダリングにクリーンアップ ステップを導入し、エンティティの画像の境界ボックスを保存します。このステップを追加すると、リスト 5 に示すように、エンティティ オブジェクトが変更されてクリーンアップ ステップが組み込まれます。
      リスト 5. 単一ボックスのクリアリングを含むエンティティ

      XML/HTML コードコンテンツをクリップボードにコピー
      1. var エンティティ = 関数() {
      2. /**  
      3. 初期化 および その他のメソッド
      4. **/
      5. /**  
      6. * エンティティを描画するためのレンダリング呼び出し
      7. *
      8. * @param {CanvasRenderingContext2D} コンテキスト
      9.      */
      10. this.render = 関数(コンテキスト) {
      11. context.clearRect(
      12. this.prevX、
      13. this.prevY、
      14. this.width、
      15. この.高さ);
      16. context.drawImage(this.image, this.x, this.y);
      17. thisthis.prevX = this.x;
      18. thisthis.prevY = this.y; }
      19. };
      20. レンダリング関数の更新により、通常のdrawImageの前に発生するclearRect呼び出しが導入されました。このステップでは、オブジェクトは以前の場所を保存する必要があります。図 6 は、前の位置に対して被験者がとったステップを示しています。
      図 6. 透明な長方形


      更新ステップの前に呼び出される各エンティティのクリア メソッドを作成することで、このレンダリング ソリューションを実装できます (ただし、この記事ではクリア メソッドは使用しません)。リスト 6 に示すように、このクリアリング戦略を PanningEntity に導入して、地上および雲のエンティティにクリアリングを追加することもできます。
      リスト 6. 単一ボックスをクリアする PanningEntity201558170256838.jpg (333×299)




      XML/HTML コード

      コンテンツをクリップボードにコピー
      1. var PanningEntity = 函數())     /**  
      2.      初始化及其他方法   
      3.      **/   
      4.     
      5.     /**  
      6.      * 渲染呼叫以繪製平移實體  
      7.      *   
      8.      * @param {CanvasRenderingContext2D} 上下文  
      9.      */   
      10.     
      11. this.render = 函數(情境)         context.clearRect(   
      12.             this.x,  
      13.             此.y,  
      14.             context.canvas.width,   
      15.             此.height);   
      16.         context.drawImage(   
      17.             此.image,   
      18.             this.x - this.width,  
      19.             this.y - this.height);   
      20.         context.drawImage(   
      21.             此.image,   
      22.             this.x,  
      23.             此.y);   
      24.         context.drawImage(   
      25.             此.image,   
      26.             this.x   this.width,   
      27.             this.y   this.height);   
      28.     }   
      29. };  
      30. 因為PanningEntity橫跨了整個視區,所以您可以使用畫布寬度作為清除矩形的大小。如果使用此清除策略,則會為您提供已為雲、小山和地面實體定義的重繪區域。

        為了進一步最佳化雲端實體,可以將雲分離為單獨的實體,使用它們自己的重繪區域。這樣做會大幅減少在雲端重繪區域內要清除的螢幕空間量。圖 7顯示了新的重繪區域。
        圖 7.具有單獨重繪區域的雲
        201558170334513.jpg (300×169)

        單一實體清除策略產生的解決方案可以解決像本例這樣的分層畫布遊戲上的大多數問題,但仍然可以對它進行最佳化。為了尋找針對該渲染策略的極端情況,我們假設球會與三角形碰撞。如果兩個實體碰撞,實體的重繪區域就有可能發生重疊,並建立一個不想要的渲染構件。另一個清除最佳化,更適合可能會碰撞的實體,它也將有益於分層。
        髒矩形清除

        若沒有單一清除策略,髒矩形清除策略可以是功能強大的替代品。您可以對有重繪區域的大量實體使用這種清除策略,這種實體包括密集的粒子系統,或有小行星的空間遊戲。

        從概念上講,演算法會收集由演算法管理的所有實體的重繪區域,並在清除呼叫中清除整個區域。為了增加最佳化,此清除策略也會刪除每個獨立實體產生的重複清除調用,如清單 7所示。
        清單 7.DirtyRectManager
         

        XML/HTML Code複製內容到剪貼簿
        1. var DirtyRectManager = 函數())
        2.     //將左邊緣與上方緣設為盡可能大的數值   
        3.     //(畫布寬度)由右至下至最小   
        4.     
        5.     // 隨著增加更多實體,左側和頂部將會縮小   
        6.     this.left   = 畫布    
        7. this.top
        8.     = 畫布         //隨著增加更多實體,右邊和底部將會增加   
        9.     
        10. this.right  = 
        11. 0     this.bottom = 0
        12. ;  >0;   ;     
        13.     //髒檢查以避免未新增任何實體時清除   
        14.     
        15. this.isDirty
        16.  = 
        17.          //其他初始化代碼  
        18.     
        19.     /**  
        20.      * 其他實用方法   
        21.      */   
        22.     
        23.     /**   
        24.      * 新增髒矩形參數並將此區域標示為髒  
        25.      *    
        26.      * @param {number} x   
        27.      * @param {number} y   
        28.      * @param {number} 寬度  
        29.      * @param {number} 身高   
        30.      */   
        31.     this.addDirtyRect =  >         //計算出長方形邊   
        32.         var 
        33.    = x   = x        var 
        34.   = x  = x  = x        var 上方
        35.     = y         var 底部 = 
        36.              // 左與實體左的最小值            這個.左
        37.    = 左      左   : this.left;   
        38.         // 權利實體權利的最大值  
        39.         this.right  = 右          //上實體上方的最小值         
        40. this.top    = 頂部 
        41.  this.top        上方    : this.top;            // 底部與實體底部的最大值   
        42.         
        43. this.bottom = 底部               this.isDirty = true
        44. ;
        45.     };   
        46.          /**  
        47.      * 若管理器髒了,清除長方形區域   
        48.      *   
        49.      * @param {CanvasRenderingContext2D} 上下文        */   
        50.     
        51. this.clearRect
        52.  = 
        53. 函數
        54. (上下文)🎜>
        55. 函數(上下文)
        56.         if (!this.isDirty) {   
        57.              返回;  
        58.         }  
        59.     
        60.         // 清除所計算的長方形  
        61.         context.clearRect(   
        62.             此.left,  
        63.             this.top,   
        64.             此.右 - 此.左,  
        65.             this.bottom - this.top);   
        66.     
        67.         //重置基本值  
        68.         this.left   = 畫布        
        69. this.top    = 畫布         this.right  = 
        70.         this.bottom = 0        
        71. this.isDirty = ;
        72.     }    };   將報表邏輯整合到渲染循環,這需要在清單呼叫7中的管理器之前進行渲染呼叫。將實體新增至管理器,使管理器可以在清晰的時候計算出清晰的報表維度。會產生預期的最佳化,但根據遊戲循環,管理器能夠針對遊戲循環進行最佳化,如圖8所示。
        73. 圖8.交互層的重繪區域
        74.     畫格 1 – 實體重繪區域重疊,幾乎重疊。     畫格 2 – 實體重繪區域是重疊的。     畫面 3 – 重繪區域重疊,並收集到一個重疊中。
        75. 第 4 章 – 髒版圖形已清晰。
        圖8顯示了由針對交換層的實體的演算法計算出的重繪區域。因為遊戲在這一層上包含交換,所以演算法提出解決交換和重疊的重繪區域問題。

        作為清除的重寫

        對於在恆定重繪區域中動畫的完全不透明實體,可以使用重寫作為一個最佳化技術。將不透明的位圖渲染為一個區域(預設的合成操作),這將像素放置在該區域上中,需要考慮該區域中的原始渲染。這個優化消除了渲染調用之前所需的清除調用,因為渲染會覆蓋原來的區域。 201558170548392.jpg (255×195)

        透過在渲染的上方重新渲染先前的影像,重寫可以加速地面實體。也可以透過相同的方式加速最大的層,例如背景。
        1. 透過減少每一層的重繪區域,您已經有效地找到層及其所包含的實體最佳化策略。

          結束語
        對分區進行分層是一個可以評估所有洪水即時場景的最佳化策略。如果想要利用分層實現最佳化,您需要透過分析場景的重繪區域來考慮場景如何重疊這些區域。其中一些場景是具有重疊的重繪區域的集合,可以因此定義分層,它們是渲染分層複製的良好選擇。如果您需要粒子系統或大量物理物件碰撞在一起,對分層進行分層可能是一個很好的最佳化選擇。


相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!