これは、第 3 回 360 フロントエンド スター プロジェクトの選択の宿題の質問です。 600名以上の学生が解答に参加し、最終的に60名が合格しました。これら 60 人の学生は、アイデア、コーディング スタイル、および機能の完成度に非常に期待を寄せていますが、たとえば、多くの学生がニーズに応じて完全な機能を実装できることもわかりました。 、しかし、彼らはそれを行う方法を知りませんオープンデザインAPI、つまり、何をオープンにし、何をカプセル化するかを決定するために、製品のニーズと将来の変更を分析および予測する方法。答えが正しいかどうかではなく、経験が重要です。
ここでは、このバージョンが最適であるという意味ではありませんが、このバージョンを通じて、このような複雑な UI 要件や実装に遭遇したときにどのように考えるべきかを分析できるようにするためです。
コンポーネント設計の一般的な手順
コンポーネント設計には通常、次のプロセスが含まれます:
要件の理解
技術的な選択
構造(UI)設計
-
と API 設計
プロセス設計
互換性と詳細の最適化
ツールとエンジニアリング
これらのプロセスは、すべてのコンポーネントを設計するときに発生するわけではありませんが、一般的に言えば、プロジェクトの実行中に解決する必要がある問題が発生します。プロセスの一部。以下で簡単に分析してみましょう。
要件を理解する
割り当て自体は、共通の ジェスチャーパスワード UI インタラクションを設計することだけであり、パスワードの確認とパスワードの設定を選択することで 2 つの 状態 を切り替えることができます。したがって、ほとんどの学生は、ニーズに応じてコンポーネント全体の状態切り替えとプロセスをカプセル化します。一部の学生は、特定の UI スタイルの構成機能を提供しますが、基本的に、プロセスと状態切り替えプロセスでノードを開くことはできません。実際、このコンポーネントをユーザーが使用する場合は、明らかにプロセス ノードを開く必要があります。つまり、パスワードの設定プロセス、パスワードの検証プロセスでどのような操作を実行するかをユーザーが決定する必要があります。パスワード検証が成功した後にどのような操作を実行するかは、コンポーネント開発者がユーザーに代わって決定することはできません。
var password = '11121323'; var locker = new HandLock.Locker({ container: document.querySelector('#handlock'), check: { checked: function(res){ if(res.err){ console.error(res.err); //密码错误或长度太短 [执行操作...] }else{ console.log(`正确,密码是:${res.records}`); [执行操作...] } }, }, update:{ beforeRepeat: function(res){ if(res.err){ console.error(res.err); //密码长度太短 [执行操作...] }else{ console.log(`密码初次输入完成,等待重复输入`); [执行操作...] } }, afterRepeat: function(res){ if(res.err){ console.error(res.err); //密码长度太短或者两次密码输入不一致 [执行操作...] }else{ console.log(`密码更新完成,新密码是:${res.records}`); [执行操作...] } }, } }); locker.check(password);
技術的な選択
この問題の UI 表示の中核は、9 正方形のグリッドと選択された小さなドットです。技術的に言えば、DOM/Canvas/SVG の 3 つすべてがメインです。 UIも実装可能です。
DOM を使用する場合、最も簡単な方法は、応答性に優れたフレックス レイアウトを使用することです。
DOMで描画を実現
DOMを使うメリットは、レスポンシブ実装が簡単、イベント処理がシンプル、レイアウトが複雑ではない(ただしCanvasより若干複雑)ですが、長さや傾きがスラッシュの部分(デモでは描かれていません) 計算が必要です。
DOM の使用に加えて、Canvas を使用して描画することも非常に便利です。
Canvas は描画を実装します
Canvas の使用には 2 つの細かい点があります。 1 つ目は、DOM を使用して正方形を構築することができます。コンテナ:
#container { position: relative; overflow: hidden; width: 100%; padding-top: 100%; height: 0px; background-color: white; }
ここでは、padding-top:100% を使用して、コンテナの幅と等しくなるようにコンテナの高さを拡張します。
2 番目の詳細は、Retina スクリーン上で明確な表示効果を得るために、Canvas の幅と高さを 2 倍にしてから、transform:scale(0.5) によってコンテナの幅と高さに一致するように縮小します。 。
#container canvas{ position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.5); }
Canvas の位置は絶対的なため、そのデフォルトの幅と高さはコンテナの幅と高さとは等しくありません:
let width = 2 * container.getBoundingClientRect().width; canvas.width = canvas.height = width;
このようにして、描画することができます。 Canvas上に実線と実線を配置したUIを実装してみましょう。具体的な方法については、後のコンテンツで詳しく説明します。
最後に、SVG を使用した描画を見てみましょう:
描画の SVG 実装
SVG ネイティブ操作の API はあまり便利ではないため、ここでは Snap.svg ライブラリを使用します。実装は Canvas を使用するのと似ています。したがって、ここでは詳細には触れません。
SVG の問題は、モバイル互換性が DOM や Canvas ほど良くないことです。
上記 3 つの状況を踏まえ、最終的に Canvas を使用して実装することにしました。
構造設計
Canvas を使用して実装した場合、DOM 構造は比較的単純になります。応答性を高めるには、適応幅を備えた正方形のコンテナを実装する必要があります。このメソッドは以前に紹介しました。次に、コンテナ内に Canvas を作成します。ここで注意すべき点は、Canvas をレイヤー化する必要があるということです。これは、Canvas の描画機構において、Canvas の内容を更新するには、更新対象の領域を更新して再描画する必要があるためです。頻繁に変更されるコンテンツと基本的に変更されていないコンテンツをレイヤーで管理する必要があるため、これによりパフォーマンスが大幅に向上します。
3層に分かれています
在这里我把 UI 分别绘制在 3 个图层里,对应 3 个 Canvas。最上层只有随着手指头移动的那个线段,中间是九个点,最下层是已经绘制好的线。之所以这样分,是因为随手指头移动的那条线需要不断刷新,底下两层都不用频繁更新,但是把连好的线放在最底层是因为我要做出圆点把线的一部分遮挡住的效果。
确定圆点的位置
圆点的位置有两种定位法,第一种是九个九宫格,圆点在小九宫格的中心位置。如果认真的同学,已经发现在前面 DOM 方案里,我们就是采用这样的方式,圆点的直径为 11.1%。第二种方式是用横竖三条线把宽高四等分,圆点在这些线的交点处。
在 Canvas 里我们采用第二种方法来确定圆点(代码里的 n = 3)。
let range = Math.round(width / (n + 1)); let circles = []; //drawCircleCenters for(let i = 1; i <p> </p><p>最后一点,严格说不属于结构设计,但是因为我们的 UI 是通过触屏操作,我们需要考虑 Touch 事件处理和坐标的转换。</p><pre class="brush:php;toolbar:false">function getCanvasPoint(canvas, x, y){ let rect = canvas.getBoundingClientRect(); return { x: 2 * (x - rect.left), y: 2 * (y - rect.top), }; }
我们将 Touch 相对于屏幕的坐标转换为 Canvas 相对于画布的坐标。代码里的 2 倍是因为我们前面说了要让 retina 屏下清晰,我们将 Canvas 放大为原来的 2 倍。
API 设计
接下来我们需要设计给使用者使用的 API 了。在这里,我们将组件功能分解一下,独立出一个单纯记录手势的 Recorder。将组件功能分解为更加底层的组件,是一种简化组件设计的常用模式。
我们抽取出底层的 Recorder,让 Locker 继承 Recorder,Recorder 负责记录,Locker 管理实际的设置和验证密码的过程。
我们的 Recorder 只负责记录用户行为,由于用户操作是异步操作,我们将它设计为 Promise 规范的 API,它可以以如下方式使用:
var recorder = new HandLock.Recorder({ container: document.querySelector('#main') }); function recorded(res){ if(res.err){ console.error(res.err); recorder.clearPath(); if(res.err.message !== HandLock.Recorder.ERR_USER_CANCELED){ recorder.record().then(recorded); } }else{ console.log(res.records); recorder.record().then(recorded); } } recorder.record().then(recorded);
对于输出结果,我们简单用选中圆点的行列坐标拼接起来得到一个唯一的序列。例如 "11121323" 就是如下选择图形:
为了让 UI 显示具有灵活性,我们还可以将外观配置抽取出来。
const defaultOptions = { container: null, //创建canvas的容器,如果不填,自动在 body 上创建覆盖全屏的层 focusColor: '#e06555', //当前选中的圆的颜色 fgColor: '#d6dae5', //未选中的圆的颜色 bgColor: '#fff', //canvas背景颜色 n: 3, //圆点的数量: n x n innerRadius: 20, //圆点的内半径 outerRadius: 50, //圆点的外半径,focus 的时候显示 touchRadius: 70, //判定touch事件的圆半径 render: true, //自动渲染 customStyle: false, //自定义样式 minPoints: 4, //最小允许的点数 };
这样我们实现完整的 Recorder 对象,核心代码如下:
[...] //定义一些私有方法 const defaultOptions = { container: null, //创建canvas的容器,如果不填,自动在 body 上创建覆盖全屏的层 focusColor: '#e06555', //当前选中的圆的颜色 fgColor: '#d6dae5', //未选中的圆的颜色 bgColor: '#fff', //canvas背景颜色 n: 3, //圆点的数量: n x n innerRadius: 20, //圆点的内半径 outerRadius: 50, //圆点的外半径,focus 的时候显示 touchRadius: 70, //判定touch事件的圆半径 render: true, //自动渲染 customStyle: false, //自定义样式 minPoints: 4, //最小允许的点数 }; export default class Recorder{ static get ERR_NOT_ENOUGH_POINTS(){ return 'not enough points'; } static get ERR_USER_CANCELED(){ return 'user canceled'; } static get ERR_NO_TASK(){ return 'no task'; } constructor(options){ options = Object.assign({}, defaultOptions, options); this.options = options; this.path = []; if(options.render){ this.render(); } } render(){ if(this.circleCanvas) return false; let options = this.options; let container = options.container || document.createElement('p'); if(!options.container && !options.customStyle){ Object.assign(container.style, { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', lineHeight: '100%', overflow: 'hidden', backgroundColor: options.bgColor }); document.body.appendChild(container); } this.container = container; let {width, height} = container.getBoundingClientRect(); //画圆的 canvas,也是最外层监听事件的 canvas let circleCanvas = document.createElement('canvas'); //2 倍大小,为了支持 retina 屏 circleCanvas.width = circleCanvas.height = 2 * Math.min(width, height); if(!options.customStyle){ Object.assign(circleCanvas.style, { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%) scale(0.5)', }); } //画固定线条的 canvas let lineCanvas = circleCanvas.cloneNode(true); //画不固定线条的 canvas let moveCanvas = circleCanvas.cloneNode(true); container.appendChild(lineCanvas); container.appendChild(moveCanvas); container.appendChild(circleCanvas); this.lineCanvas = lineCanvas; this.moveCanvas = moveCanvas; this.circleCanvas = circleCanvas; this.container.addEventListener('touchmove', evt => evt.preventDefault(), {passive: false}); this.clearPath(); return true; } clearPath(){ if(!this.circleCanvas) this.render(); let {circleCanvas, lineCanvas, moveCanvas} = this, circleCtx = circleCanvas.getContext('2d'), lineCtx = lineCanvas.getContext('2d'), moveCtx = moveCanvas.getContext('2d'), width = circleCanvas.width, {n, fgColor, innerRadius} = this.options; circleCtx.clearRect(0, 0, width, width); lineCtx.clearRect(0, 0, width, width); moveCtx.clearRect(0, 0, width, width); let range = Math.round(width / (n + 1)); let circles = []; //drawCircleCenters for(let i = 1; i { this.clearPath(); }); let records = []; let handler = evt => { let {clientX, clientY} = evt.changedTouches[0], {bgColor, focusColor, innerRadius, outerRadius, touchRadius} = options, touchPoint = getCanvasPoint(moveCanvas, clientX, clientY); for(let i = 0; i { recordingTask.cancel = (res = {}) => { let promise = this.recordingTask.promise; res.err = res.err || new Error(Recorder.ERR_USER_CANCELED); circleCanvas.removeEventListener('touchstart', handler); circleCanvas.removeEventListener('touchmove', handler); document.removeEventListener('touchend', done); resolve(res); this.recordingTask = null; return promise; } let done = evt => { moveCtx.clearRect(0, 0, moveCanvas.width, moveCanvas.height); if(!records.length) return; circleCanvas.removeEventListener('touchstart', handler); circleCanvas.removeEventListener('touchmove', handler); document.removeEventListener('touchend', done); let err = null; if(records.length o.pos.join('')).join('')}; resolve(res); this.recordingTask = null; }; document.addEventListener('touchend', done); }); recordingTask.promise = promise; this.recordingTask = recordingTask; return promise; } }
它的几个公开的方法,recorder 负责记录绘制结果, clearPath 负责在画布上清除上一次记录的结果,cancel 负责终止记录过程,这是为后续流程准备的。
流程设计
接下来我们基于 Recorder 来设计设置和验证密码的流程:
验证密码
设置密码
有了前面异步 Promise API 的 Recorder,我们不难实现上面的两个流程。
验证密码的内部流程
async check(password){ if(this.mode !== Locker.MODE_CHECK){ await this.cancel(); this.mode = Locker.MODE_CHECK; } let checked = this.options.check.checked; let res = await this.record(); if(res.err && res.err.message === Locker.ERR_USER_CANCELED){ return Promise.resolve(res); } if(!res.err && password !== res.records){ res.err = new Error(Locker.ERR_PASSWORD_MISMATCH) } checked.call(this, res); this.check(password); return Promise.resolve(res); }
设置密码的内部流程
async update(){ if(this.mode !== Locker.MODE_UPDATE){ await this.cancel(); this.mode = Locker.MODE_UPDATE; } let beforeRepeat = this.options.update.beforeRepeat, afterRepeat = this.options.update.afterRepeat; let first = await this.record(); if(first.err && first.err.message === Locker.ERR_USER_CANCELED){ return Promise.resolve(first); } if(first.err){ this.update(); beforeRepeat.call(this, first); return Promise.resolve(first); } beforeRepeat.call(this, first); let second = await this.record(); if(second.err && second.err.message === Locker.ERR_USER_CANCELED){ return Promise.resolve(second); } if(!second.err && first.records !== second.records){ second.err = new Error(Locker.ERR_PASSWORD_MISMATCH); } this.update(); afterRepeat.call(this, second); return Promise.resolve(second); }
可以看到,有了 Recorder 之后,Locker 的验证和设置密码基本上就是顺着流程用 async/await 写下来就行了。
细节问题
实际手机触屏时,如果上下拖动,浏览器有默认行为,会导致页面上下移动,需要阻止 touchmove 的默认事件。
this.container.addEventListener('touchmove', evt => evt.preventDefault(), {passive: false});
这里仍然需要注意的一点是, touchmove 事件在 chrome 下默认是一个 Passive Event ,因此 addEventListener 的时候需要传参 {passive: false},否则的话不能 preventDefault。
工具 & 工程化
因为我们的代码使用了 ES6+,所以需要引入 babel 编译,我们的组件也使用 webpack 进行打包,以便于使用者在浏览器中直接引入。
这方面的内容,在之前的博客里有介绍,这里就不再一一说明。
最后,具体的代码可以直接查看 GitHub 工程 。
总结
以上就是今天要讲的全部内容,这里面有几个点我想再强调一下:
在设计 API 的时候思考真正的需求,判断什么该开放、什么该封装
做好技术调研和核心方案研究,选择合适的方案
优化和解决细节问题
以上がネイティブ JS はジェスチャ ロック解除コンポーネント インスタンス メソッドを実装します (写真)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

JavaScriptは、現代のWeb開発のコア言語であり、その多様性と柔軟性に広く使用されています。 1)フロントエンド開発:DOM操作と最新のフレームワーク(React、Vue.JS、Angularなど)を通じて、動的なWebページとシングルページアプリケーションを構築します。 2)サーバー側の開発:node.jsは、非ブロッキングI/Oモデルを使用して、高い並行性とリアルタイムアプリケーションを処理します。 3)モバイルおよびデスクトップアプリケーション開発:クロスプラットフォーム開発は、反応および電子を通じて実現され、開発効率を向上させます。

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

Pythonはデータサイエンスや機械学習により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、簡潔な構文とリッチライブラリエコシステムで知られており、データ分析とWeb開発に適しています。 2。JavaScriptは、フロントエンド開発の中核です。 node.jsはサーバー側のプログラミングをサポートしており、フルスタック開発に適しています。

JavaScriptは、最新のブラウザにすでに組み込まれているため、インストールを必要としません。開始するには、テキストエディターとブラウザのみが必要です。 1)ブラウザ環境では、タグを介してHTMLファイルを埋め込んで実行します。 2)node.js環境では、node.jsをダウンロードしてインストールした後、コマンドラインを介してJavaScriptファイルを実行します。

Quartzタイマーを使用してタスクをスケジュールする場合、Quartzでタスク通知を事前に送信する方法、タスクの実行時間はCron式によって設定されます。今...

JavaScriptプログラミング、プロトタイプチェーンの関数パラメーターの理解と操作のJavaScriptのプロトタイプチェーンの関数のパラメーターを取得する方法は、一般的で重要なタスクです...

WeChatアプレットWeb-ViewでVue.jsを使用する動的スタイルの変位障害がvue.jsを使用している理由の分析...


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

SublimeText3 中国語版
中国語版、とても使いやすい
