タスク 4(最終チャレンジ)が公開され、タスクの締切は 6 月 10 日から 6 月 30 日です。
TASK 0004 内容:https://github.com/baidu-ife/ife/tree/master/task/task0004
私がやったこと:https://github.com/DIYgod/ife-work/tree/master/task0004
オンラインデモ:https://www.anotherhome.net/file/ife/task0004
今回のタスクは断続的に 20 日(5.20-6.9)かかりました。
以下は私が TASK 0004 の過程でのいくつかの記録です。
モバイル端末対応#
「2〜3 年前を振り返ると、フロントエンドエンジニアはモバイルインターネット時代において、フロントエンドに生存空間があるのか心配していました。しかし、今日私たちのチームを見ると、フロントエンドエンジニアの半分以上の仕事はモバイル Web または APP の開発に関わっています。モバイル Web や APP は技術的にはデスクトップ Web と本質的に違いはありませんが、モバイル端末には多くの落とし穴があります。この部分を学ぶことで、デスクトップとモバイルの両方に対応できるフロントエンド開発エンジニアになれます。」
全体の製品を SPA にする必要があり、最初は全くアイデアがありませんでした。2 日間 AngularJS を見た後、最終的には自分で実装することに決めました。
Gmail を参考にしました:
別のアンカーに切り替えるときは、そのアンカーに対応する部分だけを表示し、他の部分は displayで隠します。しかし、具体的にどう実現されているのかは理解できませんでした。console でlocation.href = '#mn';
を実行しても自動的に display が変更されるので、クリックイベントではなくアンカーがバインドされていることを示しています。
李勝菊苣が URL を監視して実現していると教えてくれました。MVC のルーティングに似ています。自分で実装するのはかなり難しいと感じました。。。しかし、別の方法を思いつきました。簡単に言うと、CSS3 の target 擬似クラスを利用するということです。デモは以下の通りです:
See the Pen jPMgre by DIYgod (@DIYgod) on CodePen.
それで CSS を少し修正すれば、モバイル端末の対応が簡単に実現できました。
また李勝菊苣が私を導いてくれました。張鑫旭菊苣の Mobilebone フレームワーク(公式サイト)を分析することで、より良い実装を見つけました。上記の実装は無効になりました 23333。
原理はこうです:アンカーを切り替えると onhashchange イベントが発生するので、onhashchange イベントに関数をバインドしました。この関数は切り替え前のアンカーと切り替え後のアンカーを記録し、前後のアンカーを判断して適切なアクションを実行します。切り替えの過程で、子ページに slide out in reverse のいくつかのクラスを追加し、これらのクラスを通じてスライド効果を実現します。具体的な実装は以下の CSS 部分を参照してください。切り替えが完了した後、表示する必要のない子ページを隠し、以前に追加したクラスをクリアします。こうして実現しました。
JS 部分:
/* スライド効果 */
window.onhashchange = function () {
var newHash = location.hash;
var oldEle = $('.' + oldHash.substr(1));
var newEle = $('.' + newHash.substr(1));
if ((oldHash == '#type' && newHash == '#task') || (oldHash == '#task' && newHash == '#details')) {
oldEle.className += ' slide out';
newEle.className += ' slide in';
newEle.style.display = 'block';
oldEle.style.display = 'block';
setTimeout(function () {
newEle.style.display = 'block';
oldEle.style.display = 'none';
oldEle.className = oldEle.className.replace(/ slide out/, '');
newEle.className = newEle.className.replace(/ slide in/, '');
}, 225);
}
else if ((oldHash == '#task' && newHash == '#type') || (oldHash == '#details' && newHash == '#task')) {
newEle.className += ' slide reverse in';
oldEle.className += ' slide reverse out';
oldEle.style.display = 'block';
newEle.style.display = 'block';
setTimeout(function () {
oldEle.style.display = 'none';
newEle.style.display = 'block';
newEle.className = newEle.className.replace(/ slide reverse in/, '');
oldEle.className = oldEle.className.replace(/ slide reverse out/, '');
}, 225);
}
oldHash = newHash;
}
CSS 部分:
/* スライド効果 from mobilebone */
.slide.out, .slide.in {
animation-timing-function: ease-out;
animation-duration: 225ms;
}
.slide.in {
animation-name: slideinfromright;
}
.slide.out {
animation-name: slideouttoleft;
}
.slide.reverse.out {
animation-name: slideouttoright;
}
.slide.reverse.in {
animation-name: slideinfromleft;
}
/* sidesからのslideinのkeyframes */
@-webkit-keyframes slideinfromright {
from {
-webkit-transform: translate3d(100%, 0, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
}
}
@keyframes slideinfromright {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@-webkit-keyframes slideinfromleft {
from {
-webkit-transform: translate3d(-100%, 0, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
}
}
@keyframes slideinfromleft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* sidesへのslideoutのkeyframes */
@-webkit-keyframes slideouttoleft {
from {
-webkit-transform: translate3d(0, 0, 0);
}
to {
-webkit-transform: translate3d(-100%, 0, 0);
}
}
@keyframes slideouttoleft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@-webkit-keyframes slideouttoright {
from {
-webkit-transform: translate3d(0, 0, 0);
}
to {
-webkit-transform: translate3d(100%, 0, 0);
}
}
@keyframes slideouttoright {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
更新:この方法は Safari での動作があまり良くないことが実証されました。
再更新:上記を補足します:この方法は、どうやらモバイル端末の Safari での動作が良くないようですが、Mac 端の Safari では正常に動作します。
CSS 処理#
「CSS 言語はその自身の設計上の問題や、一部のブラウザの互換性の問題により、書く際に多くの冗長なコードを書く必要があったり、互換性のために同じスタイル設定を何度も書く必要があることがよくあります。これらの問題に対処するために、CSS のプリプロセッサやポストプロセッサの概念と関連する方法、ツールが誕生しました。
これらのツールや方法は、より効率的に保守性の高い CSS コードを書くのに役立ちます。」
調査の結果、私はより広く使われている Less を使用することに決めました。
慕課網のチュートリアル(less 即学即用)を参考にして整理した Less のマインドマップ:
CSS 部分のリファクタリングが完了し、ついに再利用できるようになりました。DRY(Don't repeat yourself)。
さらに、Grunt と組み合わせて autoprefixer を使用してブラウザのプレフィックスを処理するのは、まさに素晴らしい体験です。
セキュリティ#
「セキュリティは皆が見落としがちですが、実際には影響が非常に大きな問題が発生することがあります。特に企業開発を経験したことがない、または落とし穴を経験したことがない学生にとって、会社で働き、実際のプロジェクトを行うときに非常に容易にセキュリティ問題が発生することがあります。」
既存のプログラムには脆弱性が存在します。たとえば、タスク内容に以下の内容を入力して保存すると、私たちが定義した script スクリプトが実行されます。
<iframe src=javascript:alert('xss');height=0 width=0></iframe>
または
<img src="1" onerror=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")>
したがって、簡単に XSS 防護を行う必要があります:
ほとんどの場合、ユーザーの入力を処理し、合法的な値のみを許可し、その他の値はすべてフィルタリングします。しかし、さらに進んで、タグを変換することもできます。
入力内容を HTML エンコード処理します:
function htmlEncode(str) {
return str.replace(/&/g, "&amp;")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#x27;")
.replace(/\//g, "&#x2f;")
.replace(/\n/g, "<br>");
}
たとえば、ユーザーが入力した場合:
<iframe src=javascript:alert('xss');height=0 width=0></iframe>
保存後、最終的に保存されるのは:
<iframe src=javascript:alert(&#x27;xss&#x27;);height=0 width=0></iframe>
その後、表示時にブラウザはこれらの文字をテキスト内容として表示し、実行可能なコードの一部としてではなく表示します。
さらに、SSL が自動的に追加されます 2333
パフォーマンス最適化#
「小さなプロジェクトを行う際、学校のウェブサイトプロジェクトなど、トラフィックが日平均 500 を超えず、ほとんどがキャンパス内のローカルネットワークからのアクセスである場合や、実験室の MIS システムを開発する場合、このシステムを開発したことは一生ないでしょう。このようなプロジェクトでは、パフォーマンス最適化はしばしば無視されがちです。
しかし、日平均 PV 数万、数十万、さらにはそれ以上の規模のプロジェクトを行う場合、開発したページは全国各地、異なるネットワーク条件のユーザーによってアクセスされます。この時、パフォーマンスの問題は無視できません。今日のネットワーク条件下で、ページが 3 秒以内にファーストスクリーンのレンダリングを完了できない場合、あなたのウェブサイトは多くのユーザーを失うことになります。
ウェブサイト全体のパフォーマンス最適化には多くの段階と作業があり、ほとんどの場合、フロントエンドエンジニアだけでは完了できません。特に職能が明確に分かれている会社では、フロントエンドとバックエンド、運用、DBA などの複数の職種が協力して完了する必要があります。したがって、私たちのコースでは、パフォーマンス最適化がどのような側面に関わるかを理解することを主に目指し、同時にフロントエンド分野で特に注目すべき技術的なポイントを紹介します。」
モジュール化#
「複雑なプロジェクト、特に複数人が協力する複雑なプロジェクトにおいて、モジュールを合理的に分割し、モジュールの読み込みをより便利にし、モジュール間の依存関係を管理することは、プロジェクトチームが直面する問題です。現在、業界には AMD のような一般的な解決策がいくつか存在します。この部分では、JavaScript のモジュール化を学び、プロジェクトモジュールを合理的に計画し、モジュール化ツールを合理的に使用してプロジェクトのコード構造を最適化する方法を学んでほしいと思います。」
調査の結果、私は RequireJS を使用することに決めました。
JS の参照方法を次のように変更します。
<script src="scripts/require.js" data-main="scripts/main"></script>
次に JS を書き換え、JS を 4 つのモジュール:主モジュール gtd util selector に分けました。しかし、分け方があまり良くないと感じました。。。答弁の際に指導教官に聞いてみるしかありません。
その中で問題に直面しました:
次のような HTML 構造があります。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>デモ</title>
<style>
div {
height: 100px;
width: 100px;
background: #eee;
margin: 10px;
}
</style>
</head>
<body>
<div onclick="myClick();"></div>
<div onclick="myClick();"></div>
<div onclick="myClick();"></div>
<script src="js/main.js"></script>
</body>
</html>
これをモジュール化改造した後、明らかに myClick はグローバル関数ではなくなるため、こう呼び出すことはできません。
そこで、モジュール内で click イベントをバインドしようとしました:
define(function () {
function init() {
var myDiv = document.getElementsByTagName('div');
for (var i = 0; i < myDiv.length; i++) {
myDiv[i].addEventListener('click', myClick(myDiv[i]));
}
myDiv[0].click();
}
function myClick(ele) {
ele.innerHTML = ele.innerHTML + 'click';
}
return {
init: init
}
});
しかし、喜ばしいことにバインドを間違えました。皆さんは気づいたでしょうが、当時の私は気づきませんでした。そして、5 行目の間接的に myClick (myDiv [i]) を呼び出したため、10 行目の呼び出しの結果だと思い込んでしまいました(これはデモに過ぎず、当時の状況はこれよりも複雑でした。この 2 つの呼び出しの結果は確かに似たようなものでした)。
その結果、click バインドされた関数はモジュール内で呼び出せる(と思っていました)が、ページ内でクリックしても反応しませんでした。
そこで私は自分で推論しました:click イベントバインドの myClick 関数はグローバル関数ではなく、モジュール内でのみ有効であり、ページ内でクリックするとグローバルで myClick 関数が呼び出されるため、反応しないのだと。
一見理にかなっているように思えましたが、実際にはそうではありませんでした。V2EX で質問した後、熱心なネットユーザー7anshuaiがバインドの誤りを見抜いてくれました。
それで、次のように修正しました:
define(function () {
function init() {
var myDiv = document.getElementsByTagName('div');
for (var i = 0; i < myDiv.length; i++) {
myDiv[i].addEventListener('click', myClick);
}
myDiv[0].click();
}
function myClick() {
this.innerHTML = this.innerHTML + 'click';
}
return {
init: init
}
});
その間、私はモジュール内で関数をグローバル空間に露出させようと試みました。次のように:
window.myClick = myClick;
これは有効でしたが、非常に悪いアイデアでした。幸いにも、そのままにはしませんでした。。。
6. フロントエンドエンジニアリング
「現在、業界には非常に多くのフロントエンド開発ツールがあり、開発プロセスで自動化できる作業を完了し、開発効率を向上させ、複数人が協力する際の開発プロセスの一貫性を高め、プロジェクト全体の運用効率を向上させます。」
調査の結果、最終的に Yeoman、Bower、Grunt の 3 つのツールを組み合わせてエンジニアリング改造を行うことに決めました。
慕課網のチュートリアル(Grunt-beginner 前端自动化工具)を参考にして整理したマインドマップ:
Yeoman を使用して新しい webapp プロジェクトを作成し(VPN が必要)、他に必要なパッケージをインストールし、設定ファイルを修正すれば、自動化ツールによる非常に効率的で驚くべき体験を楽しむことができます。
ここでは、主にコードの less コンパイル、CSS プレフィックスの処理、HTML、CSS、JS の圧縮、ファイル名に md5 値を追加する処理を行いました。処理前のファイルは app フォルダに、処理後のファイルは disk フォルダにあります。
完了、卒業答弁を待っています〜