1時間で書かれたビジュアルウィンドウコンフィギュレーター

興味深い問題を解決しました-ウィンドウの視覚的なエディター構成を作成します。



開発プロセスの詳細を同僚と共有します。





UPD。 スクリーンショットを追加しました。

UPD2。 私たちはオフライン、ガラス張り、木製またはプラスチック製の窓について話している-それを通して彼らは家から通りを見ている



フィードバックをありがとう!





ビジネス要件



顧客にインタビューします。



1.これは、任意の一般的なケースで機能するサイトのモジュールです。

2.編集モードでは、プログラムはウィンドウの開口部の数と位置を指定できるようにする必要があります。

3.編集モードでは、プログラムはウィンドウの開口部を開く方法を指定できるようにします。5つのオプション:開口部なし、左、右、左とリクライニング、右とリクライニング。

4.表示モードでは、プログラムは任意の縮尺でウィンドウ構成を表示する必要があります。

5.ウィンドウのサイズ、プロポーション、色、その他の特性に関する情報を保存および操作する必要はありません。 写真はカラーで鮮明でなければなりません。 この場合のESKDはそうではありません。

6.バギーやバカであってはならず、クロスブラウザであるべきであり、タブレットブラウザやスマートフォンなどで動作するはずです。



この段階で、お客様と一緒に、Googleの画像検索インターフェースで類似の製品を検索します。 サイトを検索することで、ウィンドウの売り手を見つけ、多数のサイトにアクセスして、オンラインコンフィギュレーターのインターフェイスと一般的なウィンドウ構成の範囲を調べます。 私たちは私たちと一緒にいるべきものとすべきでないものについて議論しています。



TUおよびTK



その結果、技術的タスクを策定するために、ビジネス要件を技術的条件で補完します。

1.任意のスケーリングの要件に基づきます-グラフィックスはベクトルでなければならないという理解があります。 満足できるクロスブラウザソリューションは、HTML5キャンバスです。

2.明らかに、編集モードと表示モードの2つのモードがあるはずです。

3.編集モードでは、データは入力タイプ=非表示で保存する必要があります。 CMSに変更を加えません-なぜ追加のバントが必要なのですか? 追加および編集用のフォーム、DBMS、および対応するモデルに1つのフィールドを追加するだけです(私にとっては、実際には1つのステップで行われます。

4.編集モードでは、以前に作成されたウィンドウの視覚的構成が、入力タイプ=隠しフィールドにあるデータから自動的に復元および置換される必要があります。

5.表示モードでは、CMSkaはデータを一部のdivのプロパティとして提供します。プログラムには次のデータが必要です。a)検出、b)ウィンドウに描画

この場合、仕様は行いませんが、抵抗が最も少ない経路をたどります。 ソリューションのビジョンの大部分はすでに存在しているため、すぐに実装を開始します。



開発



厳しいプログラミングの現実:私は人生を複雑にしたくないので、最初にスケーラブルで保守可能なソリューションを作成します。 したがって、DRY、したがって抽象化とレイヤー-すぐに、デフォルトで。



さまざまな窓を通して見たとき、何を描くべきかを理解するために、鉛筆でノートに小さなカタログをスケッチしました。 これらのスケッチを行ったとき、CSSで(おそらく無駄に)これを行いたくなく、<canvas />で作業を続けたいという理解が得られました。

キャンバスを操作するためのライブラリを探します。 calebevans.me/projects/jcanvasを見つけ、すぐドキュメントを調べ、ソースの品質を評価し、これが今必要なものであることを理解します。

描画は最低レベルの機能になることを理解しています。 そして、一般的に、私は長く描きたいと思っていました。 ドキュメントでいくつかの機能を試してみます。サンドボックスでオンラインの例を見つけます。 すべてが機能し、すべてが適切です。



描画を開始



ウィンドウを描画するための基底関数を作成します。

function windows_init(selector) { window_canvas = $('<canvas></canvas>'). attr('width',window_width). attr('height',window_height). attr('background','blue'). insertAfter(selector); }
      
      





当然、関数はパラメーターを保存しません(これはデータと呼ばれます)。 内部関数は変数です。

その瞬間、良心は目覚めなかったので、彼らは可視性の世界的な分野にいます。 彼女が目覚めたら、私はすべてをクラスに入れます。 怠(または常識)と同時に目覚めたら、CoffeeScriptで書きます。 現在、星は一定の位置にあり、最終製品は12個の関数jQueryで構成される小さなプログラムになるため、そのようなアクションの実行可能性は現在考慮されていません。 最初に動作させます。 リファクタリング-その後。

スケッチを見ると、四角形のようなウィンドウの開口部を描画し、その内部の滑らかな破線の助けを借りて開口部を指定できることがわかります。



 function make_leaf(canvas, x,y, width, height, window) { canvas.drawRect({ layer: true, strokeStyle: window_silver, fillStyle: window_blue, strokeWidth: 1, x: x, y: y, width: width, height: height, fromCenter: false, }); }
      
      







今-開口部を示す線。 左-左、右-右、傾斜-リクライニング。 トランサムダウンのケースはありません(お客様にインタビューしたときに再度尋ねました)ので、今は気にしません。 必要に応じて、簡単に追加できます。

 // window opening draw function open_left(canvas, x, y, width, height) { canvas.drawLine({ strokeStyle: window_gray, strokeWidth: 1, x1: x, y1: y, x2: x + width, y2: y + (height / 2), x3: x, y3: y + height, }); } function open_right(canvas, x, y, width, height) { canvas.drawLine({ strokeStyle: window_gray, strokeWidth: 1, x1: x + width, y1: y, x2: x, y2: y + (height / 2), x3: x + width, y3: y + height, }); } function tilt(canvas, x, y, width, height) { canvas.drawLine({ strokeStyle: window_gray, strokeWidth: 1, x1: x, y1: y + height, x2: x + (width / 2), y2: y, x3: x + width, y3: y + height, }); }
      
      







これを試すための非常に簡単なテストを書いています。 すべてが機能するので、次に進みます。



ウィンドウビュー



実際には、開口部の構成に応じて、すべての窓は「垂直」(通常はアパートで行われる)、T字型に分割できます。 あまり一般的ではないのは、「水平」なものです-入り口や施設で。

まず、もっと簡単なものを描きます。 leafsパラメーターは、開口部の数です。

 function window_vertical(canvas, x, y, width, height, leafs, window) { var leaf = width / leafs; for (var i = 0; i < leafs; i++) { var leaf_x = x + (leaf * i); var leaf_y = y; var leaf_width = leaf; var leaf_height = height; var leaf_num = i; make_leaf(canvas, leaf_x, leaf_y, leaf_width, leaf_height, window, leaf_num); } }
      
      







少しのデバッグと一連の小さなテストを経て、機能を機能する形にしました。

手でパラメータを渡し、開口部を描画する関数を呼び出して、破線が上に表示されるようにします。

90度回転すると、「水平」ウィンドウが表示されます。

 function window_horisontal(canvas, x, y, width, height, leafs, window) { var leaf = height / leafs; for (var i = 0; i < leafs; i++) { var leaf_x = x; var leaf_y = y + (leaf * i); var leaf_width = width; var leaf_height = leaf; var leaf_num = i; make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num); } }
      
      







私はテストして、パフォーマンスを得ています。

美しいプロポーションは1対2です。ビジネス要件では、プロポーションに煩わされないようにという指示があるため、T字型の窓については、このような設計を行います。

 function window_t(canvas, x,y,width, height,leafs, window) { var w = width / leafs; make_leaf(canvas, x, y, width, height / 3, window, 0); for (var i = 0; i < leafs; i++) { var leaf_x = x + (w * i); var leaf_y = y + (height / 3 ); var leaf_width = w; var leaf_height = height * 2 / 3; var leaf_num = i + 1; make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num); } }
      
      







私はテストを行い、すべてがスムーズに機能するようにします。



カタログ



プログラムが動作するすべての種類のウィンドウを描画します。



 function windows_catalog() { window_horisontal( window_canvas, 0, padding, catalog_height, catalog_height, 1, {type: 'single', leafs: 1, from: 'catalog'}); var offset = catalog_height + padding; for (var i = 2; i < 5; i++) { window_vertical( window_canvas, offset, padding, catalog_height * (i / 2), catalog_height, i, {type: 'vertical', leafs: i, from: 'catalog'}); offset += padding + (catalog_height * (i / 2)); } window_horisontal( window_canvas, offset, padding, catalog_height, catalog_height, 2, {type: 'horisontal', leafs: 2, from: 'catalog'}); offset += padding + catalog_height; for (var i = 0; i < 3; i++) { window_t( window_canvas, offset, padding, catalog_height, catalog_height, i + 2, {type: 't', leafs: i + 2, from: 'catalog'}); offset += padding + catalog_height } }
      
      







7番目のパラメーターとその内容の理解は後で追加されました。 今彼を無視してください。

そして、クリック時にウィンドウサッシを描画する機能にコールバックを追加します。 コードの中間バージョンは保存されていません-適切なオーバークロックを取得し、頻繁にコミットするのを忘れていたため、最終バージョンを表示します。



 function make_leaf(canvas, x,y, width, height, window, leaf_num) { canvas.drawRect({ layer: true, strokeStyle: window_silver, fillStyle: window_blue, strokeWidth: 1, x: x, y: y, width: width, height: height, fromCenter: false, click: function(layer) { leaf_clicked(window, leaf_num) } }); }
      
      







また、ディレクトリ内の大きなウィンドウまたは小さなウィンドウのシャッターのクリックをキャッチする機能。



 function leaf_clicked(window, leaf_num) { if ( ! window) { return; } window_canvas.clearCanvas(); windows_catalog(); if (window.size == 'big') { trigger_opening(leaf_num); } big_window(window.type, window.leafs); }
      
      







個別のコールバックを作成するというアイデアがありましたが、理由の過程で、追加の作業を見つけることができませんでした。

便宜上、ディスパッチャ機能が追加されました。



 function opening(canvas, x, y, width, height, num) { switch (window_opening[num]) { case 'left': open_left(canvas, x, y, width, height); break; case 'left tilt': open_left(canvas, x, y, width, height); tilt(canvas, x, y, width, height); break; case 'right': open_right(canvas, x, y, width, height); break; case 'right tilt': open_right(canvas, x, y, width, height); tilt(canvas, x, y, width, height); break; } }
      
      







サッシ開閉切り替え



サッシを開くと、クリックして切り替わります。 何がもっと簡単だろうか?

葉のリストを配列に保存し、2番目の配列でそれらを開く可能性を判断します。

 // window opening var window_opening = []; var opening_order = ['none', 'left tilt', 'right tilt', 'left', 'right'];
      
      





配列にデフォルトのデータを入力します。 最良の選択肢ではありませんが、この記事を書いている時点では、何か他のこと、つまりデータの保存について考えていました。

 function set_opening(leaf_count) { for (var i = 0; i < leaf_count; i++) { window_opening.push(opening_order[0]); } }
      
      







クリックすると、サッシの開口部が変更されます。 オープンの可能性のサイクル:いいえ、左、右、左、後ろに傾く、右、後ろに傾く。

 function trigger_opening(num) { var current = opening_order.indexOf(window_opening[num]); if ((current + 2) > opening_order.length) { current = 0; } else { current++; } window_opening[num] = opening_order[current]; window_data(); }
      
      







そして、遠くに行かずに...



保存中



編集後のデータは保存する必要があります。

シリアル化は手動で行います。

 function window_data() { var string = order.type + '|' + order.leafs; for (var i in window_opening) { string += '|' + window_opening[i]; } var select = $('input[name="window_type"]'); select.val(string); }
      
      







また、保存されたデータからウィンドウを描画することを誰も気にしません。



 function window_from_string(string) { if ( ! string.length) { return; } var data = string.split('|'); for (var i = 0; i < 10; i++) { window_opening[i] = data[i + 2]; } big_window(data[0],data[1]); }
      
      







ウィンドウの構成は、注文のリストに描画することができ、非常に便利です。 小さな写真。

 function small_window_from_string(element, string, width, height) { if ( ! string.length) { return; } var small_canvas = $('<canvas></canvas>'). attr('width',width). attr('height',height). appendTo(element); var data = string.split('|'); for (var i = 0; i < 10; i++) { window_opening[i] = data[i + 2]; } var leafs = data[1]; switch (data[0]) { case 'single': window_vertical(small_canvas, 0, 0, width, height, leafs, false); break; case 'vertical': window_vertical(small_canvas, 0, 0, width, height, leafs, false); break; case 'horisontal': window_horisontal(small_canvas, 0, 0, width, height, leafs, false); break; case 't': window_t(small_canvas, 0, 0, width, height, leafs, false); break; } }
      
      







いつ描くか?



プログラムは、ウィンドウを描画するときだと何らかの形で理解する必要があります。

TORに基づいて、2つのオプション-フォームフィールドと任意の場所の<div />があります。

 function windows_handler() { // add or edit var select = $('input[name="window_type"]'); if (select.length) { select.hide(); windows_init(select); window_from_string(select.val()); } // show small window $('.magic_make_window').each(function() { small_window_from_string($(this),$(this).attr('window'), $(this).width(), $(this).height()) }); }
      
      







入力[name =“ window_type”]は、おそらく最良の解決策ではありません。 その瞬間、私は仕事でプログラムを開始するという目標があり、CMSをまったく変更したくなかったのです。したがって、プラグインがそのフィールドをwindows_typeという名前で検索するようにトレーニングしました。



このプログラムからライブラリを作成する場合、変数にセレクターを配置する必要があります。 そして、これをクラスでラップして、名前空間などを閉じるようにしてください。



合計



完全に再設計されたコードは次のとおりです。 これはベータ版であり、彼女は変更せずに生産に入りました。

 $(document).ready(function() { set_opening(10); }); function windows_handler() { // add or edit var select = $('input[name="window_type"]'); if (select.length) { select.hide(); windows_init(select); window_from_string(select.val()); } // show small window $('.magic_make_window').each(function() { small_window_from_string($(this),$(this).attr('window'), $(this).width(), $(this).height()) }); } function small_window_from_string(element, string, width, height) { if ( ! string.length) { return; } var small_canvas = $('<canvas></canvas>'). attr('width',width). attr('height',height). appendTo(element); var data = string.split('|'); for (var i = 0; i < 10; i++) { window_opening[i] = data[i + 2]; } var leafs = data[1]; switch (data[0]) { case 'single': window_vertical(small_canvas, 0, 0, width, height, leafs, false); break; case 'vertical': window_vertical(small_canvas, 0, 0, width, height, leafs, false); break; case 'horisontal': window_horisontal(small_canvas, 0, 0, width, height, leafs, false); break; case 't': window_t(small_canvas, 0, 0, width, height, leafs, false); break; } } function window_from_string(string) { if ( ! string.length) { return; } var data = string.split('|'); for (var i = 0; i < 10; i++) { window_opening[i] = data[i + 2]; } big_window(data[0],data[1]); } var window_width = 900; var window_height = 350; var catalog_height = window_width / 18; var padding = 15; var window_canvas; var window_blue = '#8CD3EF'; var window_silver = 'white'; var window_gray = 'black'; var order = {type: undefined, leafs: undefined}; function window_data() { var string = order.type + '|' + order.leafs; for (var i in window_opening) { string += '|' + window_opening[i]; } var select = $('input[name="window_type"]'); select.val(string); } function windows_init(selector) { window_canvas = $('<canvas></canvas>'). attr('width',window_width). attr('height',window_height). attr('background','blue'). insertAfter(selector); windows_catalog(); } function windows_catalog() { window_horisontal( window_canvas, 0, padding, catalog_height, catalog_height, 1, {type: 'single', leafs: 1, from: 'catalog'}); var offset = catalog_height + padding; for (var i = 2; i < 5; i++) { window_vertical( window_canvas, offset, padding, catalog_height * (i / 2), catalog_height, i, {type: 'vertical', leafs: i, from: 'catalog'}); offset += padding + (catalog_height * (i / 2)); } //~ for (var i = 2; i < 6; i++) //~ { window_horisontal( window_canvas, offset, padding, catalog_height, catalog_height, 2, {type: 'horisontal', leafs: 2, from: 'catalog'}); offset += padding + catalog_height; //~ } for (var i = 0; i < 3; i++) { window_t( window_canvas, offset, padding, catalog_height, catalog_height, i + 2, {type: 't', leafs: i + 2, from: 'catalog'}); offset += padding + catalog_height } } function window_t(canvas, x,y,width, height,leafs, window) { var w = width / leafs; make_leaf(canvas, x, y, width, height / 3, window, 0); for (var i = 0; i < leafs; i++) { var leaf_x = x + (w * i); var leaf_y = y + (height / 3 ); var leaf_width = w; var leaf_height = height * 2 / 3; var leaf_num = i + 1; make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num); if (window.from != 'catalog') { opening(canvas, leaf_x,leaf_y,leaf_width, leaf_height, leaf_num); } } } function window_vertical(canvas, x, y, width, height, leafs, window) { var leaf = width / leafs; for (var i = 0; i < leafs; i++) { var leaf_x = x + (leaf * i); var leaf_y = y; var leaf_width = leaf; var leaf_height = height; var leaf_num = i; make_leaf(canvas, leaf_x, leaf_y, leaf_width, leaf_height, window, leaf_num); if (window.from != 'catalog') { opening(canvas, leaf_x, leaf_y, leaf_width, leaf_height, leaf_num); } } } function window_horisontal(canvas, x, y, width, height, leafs, window) { var leaf = height / leafs; for (var i = 0; i < leafs; i++) { var leaf_x = x; var leaf_y = y + (leaf * i); var leaf_width = width; var leaf_height = leaf; var leaf_num = i; make_leaf(canvas, leaf_x,leaf_y,leaf_width, leaf_height, window, leaf_num); if (window.from != 'catalog') { opening(canvas, leaf_x,leaf_y,leaf_width, leaf_height, leaf_num); } } } function make_leaf(canvas, x,y, width, height, window, leaf_num) { canvas.drawRect({ layer: true, strokeStyle: window_silver, fillStyle: window_blue, strokeWidth: 1, x: x, y: y, width: width, height: height, fromCenter: false, click: function(layer) { leaf_clicked(window, leaf_num) } }); } function big_window(window_type, leafs) { var padding_top = catalog_height + (padding * 2); if (window_width > window_height) { var segment = window_height - padding_top; } //~ else //~ { //~ var segment = (window_width - catalog_height - (padding * 3)) / 2; //~ } order.type = window_type; order.leafs = leafs; window_data(); switch (window_type) { case 'single': window_vertical( window_canvas, 0, padding_top, segment, segment, leafs, {type: 'single', leafs: 1, size: 'big'}); break; case 'vertical': window_vertical( window_canvas, 0, padding_top, segment /2 * leafs, segment, leafs, {type: 'vertical', leafs: leafs, size: 'big'}); break; case 'horisontal': window_horisontal( window_canvas, 0, padding_top, (segment * 2) / leafs, segment, leafs, {type: 'horisontal', leafs: leafs, size: 'big'}); break; case 't': window_t( window_canvas, 0, padding_top, segment, segment, leafs, {type: 't', leafs: leafs, size: 'big'}); break; } } function leaf_clicked(window, leaf_num) { if ( ! window) { return; } window_canvas.clearCanvas(); windows_catalog(); if (window.size == 'big') { trigger_opening(leaf_num); } big_window(window.type, window.leafs); } function opening(canvas, x, y, width, height, num) { switch (window_opening[num]) { case 'left': open_left(canvas, x, y, width, height); break; case 'left tilt': open_left(canvas, x, y, width, height); tilt(canvas, x, y, width, height); break; case 'right': open_right(canvas, x, y, width, height); break; case 'right tilt': open_right(canvas, x, y, width, height); tilt(canvas, x, y, width, height); break; } } // window opening draw function open_left(canvas, x, y, width, height) { canvas.drawLine({ strokeStyle: window_gray, strokeWidth: 1, x1: x, y1: y, x2: x + width, y2: y + (height / 2), x3: x, y3: y + height, }); } function open_right(canvas, x, y, width, height) { canvas.drawLine({ strokeStyle: window_gray, strokeWidth: 1, x1: x + width, y1: y, x2: x, y2: y + (height / 2), x3: x + width, y3: y + height, }); } function tilt(canvas, x, y, width, height) { canvas.drawLine({ strokeStyle: window_gray, strokeWidth: 1, x1: x, y1: y + height, x2: x + (width / 2), y2: y, x3: x + width, y3: y + height, }); } // window opening var window_opening = []; var opening_order = ['none', 'left tilt', 'right tilt', 'left', 'right']; function set_opening(leaf_count) { for (var i = 0; i < leaf_count; i++) { window_opening.push(opening_order[0]); } } function trigger_opening(num) { var current = opening_order.indexOf(window_opening[num]); if ((current + 2) > opening_order.length) { current = 0; } else { current++; } window_opening[num] = opening_order[current]; window_data(); }
      
      







記事に示されていないもの。 windows_handler関数は、document.readyとajaxデータの正常なロードの2つのイベントに従って、別のJSコンポーネントによって開始されます。 したがって、ウィンドウはページを読み込んだ直後に描画され、インタラクティブなデータ更新がある場合は再描画されます(「ライブモード」)。

すべてのユーザーケースが実行されます。 再起動せずに何度も再描画して簡単なテストを行い、クロムとスカムをしばらく走らせたままにしておきました-メモリは消費されていません。 iPadとMacBookでクロムとサファリで同じテストを数時間行いました。 問題は見つかりませんでした。



スクリーンショット



クライアントでオンザフライで作成された小さな画像(素晴らしい印刷)





全体像。 サイズはいつか修正できます。





編集モード。 ディレクトリ内の小さなウィンドウをクリックすると、大きなウィンドウの構成が変更されます(すぐに入力タイプのデータが非表示になります)。





大きな窓のサッシをクリックすると、サッシの開きが変わります。





美人!



CMSに変更はありませんでした。 ウィンドウは、divに描画された非表示フィールドで追加および編集されます。 このスクリプトを接続するだけで、ウィンドウコンフィギュレーターを任意のワードプレスにプッシュできることがわかりました。



現在、このソリューションのおかげで、多くの新しいウィンドウが販売、注文、インストールされています。



このコードをテストとともにサンドボックスに入れるといいでしょう。 どう思いますか?



PMでコメントを報告してください。



よろしくお願いします!



All Articles