JSを使用して画像を効果的に圧縮する方法

JSを使用して画像を効果的に圧縮する方法

序文

同社のモバイル事業では、ユーザーが写真をアップロードする際に、フロントエンドで写真サイズを圧縮してからサーバーにアップロードする必要があります。これにより、モバイル端末のアップストリームトラフィックが削減され、ユーザーのアップロード待ち時間が短縮され、ユーザーエクスペリエンスが最適化されます。

ちなみに、この記事の事例はプラグインとしてまとめられ、npmにアップロードされています。npm install js-image-compressor -D でインストールして使用することができます。github からダウンロードできます。

そこで、この論文では以下の問題を解決しようとします。

  • Image オブジェクト、データ URL、Canvas、ファイル (Blob) 間の変換関係を理解し​​ます。
  • 画像圧縮の主要技術。
  • 非常に大きな画像を圧縮すると黒い画面が表示される問題。

変換関係

実際のアプリケーションで考えられるシナリオ: ほとんどの場合、ユーザーがアップロードした File オブジェクトを直接読み取り、それをキャンバスに読み書きし、Canvas API を使用して圧縮し、圧縮後に File (Blob) オブジェクトに変換して、リモート イメージ サーバーにアップロードします。場合によっては、base64 文字列を圧縮してから、それを base64 文字列に変換してリモート データベースに渡したり、File (Blob) オブジェクトに変換したりする必要があることもあります。一般的に、次のような変換関係があります。

具体的な実装

以下では、変換関係図の変換方法を一つずつ実装していきます。

file2DataUrl(ファイル、コールバック)

ユーザーがページ タグ <input type="file" /> を通じてアップロードしたローカル イメージは、日付 URL 文字列形式に直接変換されます。 FileReader ファイル読み取りコンストラクターを使用できます。 FileReader オブジェクトを使用すると、Web アプリケーションは、File または Blob オブジェクトを使用して読み取るファイルまたはデータを指定し、コンピューターに保存されているファイル (または生データ バッファー) の内容を非同期的に読み取ることができます。インスタンス メソッド readAsDataURL はファイルの内容を読み取り、それを base64 文字列に変換します。読み取り後、ファイルの内容はインスタンス属性の結果で利用できるようになります。

関数 file2DataUrl(ファイル, コールバック) {
 var リーダー = 新しい FileReader();
  reader.onload = 関数(){
    コールバック(reader.result);
  };
  reader.readAsDataURL(ファイル);
}

データ URL は、プレフィックス (data:)、データのタイプを示す MIME タイプ、テキスト以外の場合はオプションの base64 タグ、およびデータ自体の 4 つの部分で構成されます。

データ:<メディアタイプ>,<データ>

たとえば、png 形式の画像は base64 文字列に変換されます: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAgAElEQVR4XuxdB5g。

file2Image(ファイル、コールバック)

ユーザーがアップロードした写真をローカルにキャッシュし、img タグで表示したい場合は、上記の方法で変換した base64 文字列を写真の src として使用するほか、URL オブジェクトを直接使用して、File や Blob に保存されているデータの URL を参照することもできます。オブジェクト URL を使用する利点は、ファイルの内容を直接 JavaScript に読み込まなくても使用できることです。これを行うには、ファイル コンテンツが必要な場所にオブジェクト URL を指定するだけです。

関数file2Image(ファイル、コールバック) {
 var image = 新しい Image();
 var URL = window.webkitURL || window.URL;
 (URL) {
 var url = URL.createObjectURL(ファイル);
    イメージ.onload = 関数() {
      コールバック(画像);
      URL.revokeObjectURL(url);
    };
    イメージのURLをコピーします。
  } それ以外 {
    inputFile2DataUrl(ファイル、関数(dataUrl) {
      イメージ.onload = 関数() {
        コールバック(画像);
      }
      画像のサイズは、
    });
  }
}

注: オブジェクト URL を作成するには、window.URL.createObjectURL() メソッドを使用し、File または Blob オブジェクトを渡します。データが不要になった場合は、そのデータが占めているコンテンツを解放するのが最善です。ただし、オブジェクト URL を参照するコードがある限り、メモリは解放されません。メモリを手動で解放するには、オブジェクトの URL を URL.revokeObjectURL() に渡します。

url2Image(url, コールバック)

画像リンク(URL)を通じてImageオブジェクトを取得します。画像の読み込みは非同期なので、取得したImageオブジェクトを返すためにコールバック関数 callback 内に配置します。

関数 url2Image(url, コールバック) {
 var image = 新しい Image();
  イメージのURLをコピーします。
  イメージ.onload = 関数() {
    コールバック(画像);
  }
}

image2Canvas(画像)

drawlmage() メソッドを使用して、Canvas オブジェクトに Image オブジェクトを描画します。

drawImage には 3 つの構文形式があります。

void ctx.drawImage(画像、dx、dy);

void ctx.drawImage(画像、dx、dy、dWidth、dHeight);

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

パラメータ:

コンテキストに描画される画像要素。

sx は、Image に基づいて選択ボックスの左上隅の X 軸座標を描画します。

sy イメージに基づいて選択ボックスの左上隅の Y 軸座標を描画します。

sWidth は選択ボックスの幅を描画します。

sHeight は選択ボックスの幅を描画します。

dx ターゲット キャンバス上のイメージの左上隅の X 軸座標。

ターゲット キャンバス上の dy イメージの左上隅の Y 軸座標。

dWidth ターゲットキャンバスに描画されるイメージの幅。

dHeight ターゲット キャンバスに描画されるイメージの高さ。

関数 image2Canvas(画像) {
 var キャンバス = document.createElement('キャンバス');
 var ctx = canvas.getContext('2d');
  キャンバスの幅 = image.naturalWidth;
  キャンバスの高さ = image.naturalHeight;
  ctx.drawImage(画像、0、0、キャンバス.幅、キャンバス.高さ);
 キャンバスを返します。
}

canvas2DataUrl(キャンバス、品質、タイプ)

HTMLCanvasElement オブジェクトには、表示する画像を含むデータ URL を返す toDataURL(type, encodingOptions) メソッドがあります。出力形式と品質も指定できます。

パラメータは次のとおりです。

type: 画像形式。デフォルトは image/png です。

画像フォーマットが image/jpeg または image/webp として指定されている場合、encoderOptions では 0 から 1 の範囲で画像品質を選択できます。値が範囲外の場合、デフォルト値 0.92 が使用され、他のパラメータは無視されます。

関数 canvas2DataUrl(キャンバス、品質、タイプ) {
 canvas.toDataURL(type || 'image/jpeg', quality || 0.8) を返します。
}

dataUrl2Image(データUrl、コールバック)

画像リンクは base64 文字列にすることもでき、これは Image オブジェクト src に直接割り当てることができます。

関数 dataUrl2Image(dataUrl, コールバック) {
 var image = 新しい Image();
  イメージ.onload = 関数() {
    コールバック(画像);
  };
  画像のサイズは、
}

dataUrl2Blob(データUrl、タイプ)

データ URL 文字列を Blob オブジェクトに変換します。主なアイデアは、まずデータ URL のデータ部分を抽出し、atob を使用して base64 でエンコードされた文字列をデコードし、次にそれを Unicode エンコードに変換し、それを Uint8Array (8 ビットの符号なし整数配列、各要素は 1 バイト) タイプの配列に格納し、最後にそれを Blob オブジェクトに変換することです。

関数 dataUrl2Blob(dataUrl, type) {
 var data = dataUrl.split(',')[1];
 var mimePattern = /^data:(.*?)(;base64)?,/;
 var mime = dataUrl.match(mimePattern)[1];
 var binStr = atob(データ);
 var arr = 新しい Uint8Array(len);

 (var i = 0; i < len; i++) の場合 {
    arr[i] = binStr.charCodeAt(i);
  }
 新しいBlob([arr], {type: type || mime})を返します。
}

canvas2Blob(キャンバス、コールバック、品質、タイプ)

HTMLCanvasElement には、キャンバスに画像を表示するための Blob オブジェクトを作成する toBlob(callback, [type], [encoderOptions]) メソッドがあります。この画像ファイルは、ユーザー エージェントの判断により、キャッシュしたり、ローカルに保存したりできます。 2 番目のパラメータは画像形式を指定します。指定しない場合、画像タイプはデフォルトで image/png になり、解像度は 96dpi になります。 3 番目のパラメータは、image/jpeg 形式の画像の出力画像の品質を設定するために使用されます。

関数 canvas2Blob(キャンバス、コールバック、品質、タイプ){
  canvas.toBlob(function(blob) {
    コールバック(blob);
  }, タイプ || 'image/jpeg'、品質 || 0.8);
}

古いブラウザとの互換性を保つために、toBlob のポリフィル ソリューションとして、データ URL を使用して、HTMLCanvasElement プロトタイプ メソッドとして Blob メソッド dataUrl2Blob を生成できます。

HTMLCanvasElement.prototype.toBlobの場合{
 Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
  値: 関数 (コールバック、タイプ、品質) {
 dataUrl = this.toDataURL(type, quality); とします。
    コールバック(dataUrl2Blob(dataUrl));
  }
 });
}

blob2DataUrl(blob、コールバック)

Blob オブジェクトをデータ URL データに変換します。FileReader のインスタンスの readAsDataURL メソッドは、ファイルの読み取りだけでなく、Blob オブジェクト データの読み取りもサポートしているため、上記の file2DataUrl メソッドをここで再利用できます。

関数 blob2DataUrl(blob, コールバック) {
  file2DataUrl(blob、コールバック);
}

blob2Image(blob、コールバック)

Blob オブジェクトを Image オブジェクトに変換します。URL オブジェクトを通じてファイルを参照できます。また、Blob などのファイルのようなオブジェクトを参照することもできます。同様に、上記の file2Image メソッドを再利用できます。

関数blob2Image(blob, コールバック) {
  file2Image(blob、コールバック);
}

アップロード(URL、ファイル、コールバック)

画像 (圧縮済み) をアップロードするには、FormData を使用してファイル オブジェクトを渡し、XHR 経由でファイルを直接サーバーにアップロードします。

関数アップロード(URL、ファイル、コールバック) {
 var xhr = 新しい XMLHttpRequest();
 var fd = 新しいFormData();
  fd.append('ファイル', ファイル);
  xhr.onreadystatechange = 関数 () {
 xhr.readyState === 4 && xhr.status === 200 の場合 {
 //アップロード成功コールバック && callback(xhr.responseText);
    } それ以外 {
 新しいエラーをスローします(xhr);
    }
  }
  xhr.open('POST', url, true);
  xhr.send(fd);
}

FileReaderを使用してファイルの内容を読み取り、アップロード用にバイナリに変換することもできます。

関数アップロード(url, ファイル) {
 var リーダー = 新しい FileReader();
 var xhr = 新しい XMLHttpRequest();

  xhr.open('POST', url, true);
  xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');

  リーダー.onload = 関数() {
    xhr.send(リーダーの結果);
  };
  reader.readAsBinaryString(ファイル);
}

シンプルな画像圧縮の実装

上記のさまざまな画像変換方法の具体的な実装を理解した後、それらを共通オブジェクト util にカプセル化し、圧縮変換フローチャートと組み合わせると、ここでは画像圧縮を簡単に実装できます。
まず、アップロードされた画像は Image オブジェクトに変換され、次に Canvas キャンバスに書き込まれ、最後に Canvas オブジェクト API によって画像のサイズと出力が調整され、圧縮が実現されます。

/**
 * シンプルな画像圧縮方法* @param {Object} オプション関連のパラメータ*/
(関数 (win) {
 var REGEXP_IMAGE_TYPE = /^image\//;
 var util = {};
 var デフォルトオプション = {
    ファイル: null、
    品質: 0.8
  };
 var isFunc = function (fn) { return typeof fn === 'function'; };
 var isImageType = function (値) { return REGEXP_IMAGE_TYPE.test(値); };

 /**
   * シンプルな画像圧縮コンストラクター * @param {Object} オプション関連のパラメーター */
 関数SimpleImageCompressor(オプション) {
    オプション = Object.assign({}, defaultOptions, オプション);
 this.options = オプション;
 this.file = オプション.file;
 これを初期化します。
  }

 var _proto = SimpleImageCompressor.prototype;
  win.SimpleImageCompressor = SimpleImageCompressor;

 /**
   * 初期化 */
  _proto.init = 関数init() {
 var _this = これ;
 var ファイル = this.file;
 var オプション = this.options;

 if (!file || !isImageType(file.type)) {
      console.error('画像ファイルをアップロードしてください!');
 戻る;
    }

 if (!isImageType(options.mimeType)) {
      オプション.mimeType = ファイル.type;
    }

    util.file2Image(ファイル、関数(画像) {
 var キャンバス = util.image2Canvas(img);
      ファイルの幅 = img.naturalWidth;
      ファイルの高さ = img.naturalHeight;
      _this.beforeCompress(ファイル、キャンバス);

      util.canvas2Blob(キャンバス、関数(blob) {
        キャンバスの幅をピクセル単位で指定します。
        キャンバスの高さを blob に設定します。
        オプション.成功 && オプション.成功(blob);
      }、options.quality、options.mimeType)
    })
  }

 /**
   * 圧縮前、画像フック関数の読み取り後 */
  _proto.beforeCompress = 関数beforeCompress() {
 if (isFunc(this.options.beforeCompress)) {
 this.options.beforeCompress(this.file);
    }
  }

 // `util` パブリック メソッドの定義を省略 // ...

 // インスタンスの静的プロパティに `util` パブリックメソッドを追加します for (key in util) {
 (util.hasOwnProperty(キー))の場合{
      SimpleImageCompressor[キー] = util[キー];
    }
  }
})(ウィンドウ)

この単純な画像圧縮メソッドの呼び出しと入力パラメータ:

var fileEle = document.getElementById('file');

fileEle.addEventListener('change', 関数() {
  ファイル = this.files[0];

 var オプション = {
    ファイル: ファイル、
    品質: 0.6、
    mimeタイプ: 'image/jpeg',
 // 圧縮前のコールバック beforeCompress: function (result) {
      console.log('圧縮前の画像サイズ: ', result.size);
      console.log('MIMEタイプ: ', result.type);
 // アップロードした画像をページ上でプレビューします // SimpleImageCompressor.file2DataUrl(result, function (url) {
 // document.getElementById('origin').src = url;
 // })
    },
 // 圧縮成功コールバック success: function (result) {
      console.log('圧縮後の画像サイズ: ', result.size);
      console.log('MIMEタイプ: ', result.type);
      console.log('圧縮率: ', (result.size / file.size * 100).toFixed(2) + '%');

 // 圧縮された画像を生成し、ページに表示します // SimpleImageCompressor.file2DataUrl(result, function (url) {
 // document.getElementById('output').src = url;
 // })

 // リモート サーバーにアップロード // SimpleImageCompressor.upload('/upload.png', result);
    }
  };

 新しい SimpleImageCompressor(オプション);
}、 間違い);

このデモが単純すぎることに抵抗がなければ、ここをクリックして試してみることができます。複数の種類の写真をアップロードするのに十分な忍耐力がある場合、次の問題がまだ残っていることがわかります。

圧縮された出力画像のサイズは元の画像サイズに固定されますが、実際には、サイズを圧縮するという目的を達成しながら出力画像のサイズを制御する必要がある場合があります。

png形式の画像はそのまま圧縮されるため、圧縮率が高くなく、「減るどころか増える」という現象が起きる場合があります。

場合によっては、他の形式を PNG 形式に変換すると、「減少ではなく増加」現象が発生することもあります。

大きなサイズの PNG 形式の画像は、一部の携帯電話では圧縮後に黒い画面に表示される場合があります。

画像圧縮の改善

「ローマは一日にして成らず」ということわざにもあるように、上記の実験を通して、多くの欠点が見つかりました。以下では、問題を一つずつ分析し、解決策を探っていきます。

圧縮された出力画像のサイズは元の画像サイズに固定されますが、実際には、サイズを圧縮するという目的を達成しながら出力画像のサイズを制御する必要がある場合があります。

圧縮された画像の変形を避けるために、一般的には幾何学的スケーリングが使用されます。まず、元の画像のアスペクト比を計算する必要があります。ユーザーが設定した高さにアスペクト比を掛けて、幾何学的スケーリング後の幅を取得します。ユーザーが設定した幅よりも小さい場合は、ユーザーが設定した高さがスケーリングの基準として使用されます。それ以外の場合は、幅がスケーリングの基準として使用されます。

var アスペクト比 = 自然な幅 / 自然な高さ;
var width = Math.max(options.width, 0) || naturalWidth;
var height = Math.max(options.height, 0) || naturalHeight;
高さ * アスペクト比 > 幅の場合 {
  高さ = 幅 / アスペクト比;
} それ以外 {
  幅 = 高さ * アスペクト比;
}

出力画像のサイズが決まりました。次のステップでは、このサイズに合わせてキャンバスを作成し、その上に画像を描画します。ここで、上記の image2Canvas メソッドを少し変更することができます。

関数 image2Canvas(image, destWidth, destHeight) {
 var キャンバス = document.createElement('キャンバス');
 var ctx = canvas.getContext('2d');
  キャンバスの幅 = destWidth || image.naturalWidth;
  キャンバスの高さ = destHeight || image.naturalHeight;
  ctx.drawImage(画像、0、0、キャンバス.幅、キャンバス.高さ);
 キャンバスを返します。
}

png形式の画像はそのまま圧縮されるため、圧縮率が高くなく、「減るどころか増える」という現象が起きることがある

一般的に、png 形式の画像を独自の形式に圧縮することは推奨されません。圧縮率が理想的ではなく、場合によっては画像の品質が悪化する可能性があるためです。

「具体的な実装」には 2 つの圧縮キー API があるためです。

toBlob(callback, [type], [encoderOptions]) encodingOptions パラメータは、image/jpeg 形式の画像の出力画像の品質を設定するために使用されます。

toDataURL(type, encodingOptions パラメータencoderOptions 画像フォーマットをimage/jpegまたはimage/webpに指定した場合、画質を0~1の範囲で選択できます。

どちらも png 形式の画像に圧縮効果はありません。

妥協案があります。しきい値を設定できます。png 画像の品質がこの値より低い場合は、png 形式で圧縮して出力します。これにより、最悪の出力結果がそれほど悪くなりません。これに基づいて、圧縮された画像のサイズが「減少するのではなく増加する」場合は、出力ソース画像をユーザーに処理します。画像の品質が一定値を超える場合は、jpeg 形式に圧縮します。

// `png` 形式の画像サイズが `convertSize` を超える場合は、`jpeg` 形式に変換します。if (file.size > options.convertSize && options.mimeType === 'image/png') {
  オプション.mimeType = 'image/jpeg';
}
// 一部のコードを省略 // ...
// ユーザーが期待する出力の幅と高さがソース画像の幅と高さより大きくない場合、出力ファイルのサイズはソースファイルより大きくなり、ソースファイルが返されます if (result.size > file.size && !(options.width > naturalWidth || options.height > naturalHeight)) {
  結果 = ファイル;
}

大きなサイズの PNG 形式の画像は、一部の携帯電話では圧縮後に黒い画面に表示される場合があります。

主要なブラウザはそれぞれ異なる最大キャンバスサイズをサポートしているため

画像サイズが大きすぎる場合、同じサイズのキャンバスを作成してその上に画像を描画すると、生成されたキャンバスに画像ピクセルがなく、キャンバス自体のデフォルトの背景色が黒になるという異常な状況が発生し、画像が「黒い画面」のように表示されます。

ここでは、出力画像の最大幅と高さを制御することで、生成されたキャンバスが範囲外になるのを防ぎ、デフォルトの黒い背景を透明色で覆うことで「黒い画面」の問題を解決できます。

// ...
// 最小および最大の幅と高さを制限します var maxWidth = Math.max(options.maxWidth, 0) || Infinity;
var maxHeight = Math.max(options.maxHeight, 0) || 無限大;
var minWidth = Math.max(options.minWidth, 0) || 0;
var minHeight = Math.max(options.minHeight, 0) || 0;

最大幅 < 無限大 && 最大高さ < 無限大) {
 (最大高さ * アスペクト比 > 最大幅)の場合 {
    最大高さ = 最大幅 / アスペクト比;
  } それ以外 {
    最大幅 = 最大高さ * アスペクト比;
  }
} そうでなければ(最大幅<無限大){
  最大高さ = 最大幅 / アスペクト比;
} そうでなければ(最大高さ<無限大){
  最大幅 = 最大高さ * アスペクト比;
}

最小幅 > 0 && 最小高さ > 0 の場合 {
 最小高さ * アスペクト比 > 最小幅の場合 {
    minHeight = minWidth / アスペクト比;
  } それ以外 {
    最小幅 = 最小高さ * アスペクト比;
  }
} それ以外の場合 (minWidth > 0) {
  minHeight = minWidth / アスペクト比;
} そうでなければ (minHeight > 0) {
  最小幅 = 最小高さ * アスペクト比;
}

幅 = Math.floor(Math.min(Math.max(幅, 最小幅), 最大幅));
高さ = Math.floor(Math.min(Math.max(高さ, minHeight), maxHeight));

// ...
// デフォルトの塗りつぶし色 (#000) を上書きします
var fillStyle = '透明';
コンテキストの塗りつぶしスタイル = 塗りつぶしスタイル;

この時点で、上記の予想外の問題は一つずつ解決されました

要約する

ページタグ <input type="file" /> を介したローカル画像のアップロードから画像圧縮までのプロセス全体を整理し、実際の使用時にまだ存在するいくつかの予期しない状況もカバーし、対応するソリューションを提供しました。画像圧縮の改良版をプラグインとしてまとめ、npm にアップロードしました。npm install js-image-compressor -D でインストールして使用できます。github からダウンロードできます。

上記は、JS を使用して画像を効果的に圧縮する方法の詳細です。JS による画像の効果的な圧縮の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • jsは画像の純粋なフロントエンド圧縮を実現します
  • 画像を圧縮する js の例 (画像サイズは変更せず、容量のみを削減)
  • js 経由で圧縮画像アップロード機能を実装する
  • JSはアップロードされた画像のbase64の長さを圧縮する機能を実現します
  • js を使用して layui にアップロードされた画像を圧縮する
  • JavaScript を使用して画像を圧縮および暗号化する方法をご存知ですか?

<<:  MySQLのSQLモードの特徴のまとめ

>>:  Zookeeper&Kafka クラスターを構築するための Docker の実装

推薦する

MySQL スケジュールタスク (EVENT イベント) を詳細に設定する方法

目次1. イベントとは何ですか? 2. 「イベント」機能を有効にする1. 機能が有効になっているかど...

Linux コマンド sort、uniq、tr ツールの詳細な説明

並べ替えツールLinux の sort コマンドは、テキスト ファイルの内容を並べ替えるために使用さ...

Centos7 に mysql と mysqlclient をインストールする際に遭遇する落とし穴の概要

1. MySQL Yumリポジトリを追加するMySQL公式サイト>ダウンロード>MySQ...

HTMLのセマンティクスといくつかの簡単な最適化についての簡単な説明

1. セマンティゼーションとは何ですか? Bing辞書の説明セマンティクス化とは、適切な HTML ...

Nginx で Http、Https、WS、WSS を設定する方法

前面に書かれた今日のインターネット分野では、Nginx は最も広く使用されているプロキシ サーバーの...

dockerでredis5.0.3をインストールする方法

1. 公式5.0.3イメージを取得する [root@localhost ~]# docker pul...

Dockerコンテナでユーザーを切り替えるときに権限が不足する問題を解決する方法

Docker コンテナでユーザーを切り替えると、権限が不十分であるというメッセージが表示されます。解...

MySQL グループレプリケーションの設定手順 (推奨)

MySQL-Group-Replication は、MySQL-5.7.17 で開発された新しい機...

Windows 上で Nginx+Tomcat クラスタを実装するプロセスの分析

導入: Nginx (エンジン エックスと同じ発音) は、BSD のようなプロトコルに基づいてリリー...

HTML の基礎_一般的なタグ、共通タグ、表

パート 1 HTML <html> -- 開始タグ<ヘッド>ウェブページ上の...

HTMLを教える記事

アーティストになるつもりがない場合は、開発者として HTML を読んで、必要に応じて簡単な変更を加え...

JSscriptタグの属性は何ですか

JS スクリプト タグの属性は何ですか? charset : オプション。 src 属性で指定された...

SQL 挿入文の書き方の説明

方法 1: INSERT INTO t1(field1,field2) VALUE(v001,v00...

ウェブページのフッターで注意すべきことのまとめ

たくさんのリンクおそらく、このようなサイトをたくさん見たことがあるでしょう。ページの下部に 50 個...

js データ型とその判定方法の例

js データ型基本データ型: 数値、文字列、ブール値、未定義、null、シンボル、参照データ型: オ...