LoginSignup
24
25

More than 3 years have passed since last update.

ブラウザ関連のJavaScript

Last updated at Posted at 2019-12-22

ユーザー情報の取得については、こちらをご参照ください。
JavaScriptによるユーザー情報の取得

ページ表示時に処理

イベント 内容
DOMContentLoaded HTMLドキュメント解析完了時(deferを指定しているなら不要)
load 全リソースの読み込み完了時(キャッシュの場合は対象外)
pageshow 全リソースの読み込み完了時(キャッシュの場合も対象)
js
window.addEventListener("load", () => {
  console.log("ページが読み込まれた");
});

ページ非表示時に処理

イベント 内容
beforeunload ページ遷移直前
unload ウィンドウを閉じた時、他のページに切り替えた時、ページをリローした時に発生(キャッシュの場合は対象外)
pagehide ページ遷移などで元のページが隠れた時
js
window.addEventListener("beforeunload", () => {
  event.returnValue;
});

タブがバックグラウンドになったときに処理

イベント 内容
visibilitychange タブのコンテンツが表示されたとき、非表示(バックグラウンド)になったとき

document要素に対して設定します。
document.visibilityStateと組み合わせて使います。

js
document.addEventListener('visibilitychange', () => {

  if (document.visibilityState === 'visible') {
    console.log("コンテンツが表示された");
  }

  if (document.visibilityState === 'hidden') {
    console.log("コンテンツがバックグラウンドになった");
  }

})

ページのフォーカスのイベント

ページにフォーカスされた時

JavaScript
window.addEventListener("focus", () => {
  console.log("ページにフォーカスされた");
});

ページからフォーカスが外れた時

JavaScript
window.addEventListener("blur", () => {
  console.log("ページからフォーカスが外れた");
});

タイトル

タイトルの取得

html
<button id="js-getTitleButton">タイトル取得</button>
JavaScript
const getTitleButton = document.getElementById("js-getTitleButton");

getTitleButton.addEventListener("click", () => {
  console.log(document.title);
});

タイトルの変更

html
<button id="js-changeTitleButton">タイトル変更</button>
JavaScript
const changeTitleButton = document.getElementById("js-changeTitleButton");

changeTitleButton.addEventListener("click", () => {
  document.title = "new title"
});

ハッシュ

値の取得

htm
<button id="js-getHashButton">ハッシュ取得</button>
JavaScript
const getHashButton = document.getElementById("js-getHashButton");

getHashButton.addEventListener("click", () => {
  console.log(location.hash);
});

変更イベント

html
<button id="js-changeHashButton">ハッシュ変更</button>
JavaScript
const changeHashButton = document.getElementById("js-changeHashButton");

//ハッシュ変更        
changeHashButton.addEventListener("click", () => {
  location.hash = "sample";
});

//ハッシュ変更監視
window.addEventListener("hashchange", () => {
  console.log("ハッシュが変更された");
});

ダイアログ

アラート

html
<button id="js-alertButton">アラート</button>
JavaScript
const alertButton = document.getElementById("js-alertButton");

alertButton.addEventListener("click", () => {
  alert("アラート");
});

コンファーム

html
<button id="js-confirmButton">コンファーム</button>
JavaScript
const confirmButton = document.getElementById("js-confirmButton");

confirmButton.addEventListener("click", () => {

  const confirmReaction = confirm("OK or Cancel");

  if (confirmReaction === true) {
    console.log("OKを選択した");
  }

  if (confirmReaction === false) {
    console.log("Cancelを選択した");
  }

});

文字入力プロンプト

html
<button id="js-promptButton">プロンプト</button>
JavaScript
const promptButton = document.getElementById("js-promptButton");
promptButton.addEventListener("click", () => {
  const promptReaction = prompt("文字入力してください", "デフォルト文字列")

  if (promptReaction === "") {
    console.log("入力されなかった");
  } else if (promptReaction == null) {
    console.log("キャンセルされた");
  } else {
    console.log(`${promptReaction}と入力された`);
  }

});

ウィンドウサイズ

プロパティ
window.innerWidth ビューポートの幅
window.innerHeight ビューポートの高さ
window.document.documentElement.clientWidth (スクロールバーを除く)
window.document.documentElement.clientHeight (スクロールバーを除く)
html
<button id="js-windowSizeButton">ウィンドウサイズ</button>
JavaScript
const windowSizeButton = document.getElementById("js-windowSizeButton");

windowSizeButton.addEventListener("click", () => {

  console.log(`幅${window.innerWidth}px`);
  console.log(`高さ${window.innerHeight}px`);

});

スクロール

表示位置の取得

ドキュメント左上基準で取得されます。

プロパティ 内容
window.scrollX X軸方向のスクロール量
window.scrollY Y軸方向のスクロール量
html
<button id="js-scrollPositionButton" style="margin-top:2000px;">スクロール位置取得</button>
JavaScript
const scrollPositionButton = document.getElementById("js-scrollPositionButton");

scrollPositionButton.addEventListener("click", () => {
  console.log(`X軸${window.scrollX}`);
  console.log(`Y軸${window.scrollY}`);
});

表示位置の変更

メソッド 内容
scrollTo(X,Y) スクロールする座標の指定(絶対)
scrollBy(X,Y) スクロールする座標の指定(相対)
html
<button id="js-scrollButton" style="margin-top:2000px;">スクロールボタン</button>
JavaScript
const scrollButton = document.getElementById("js-scrollButton");

scrollButton.addEventListener("click", () => {
  window.scrollTo(0, 0);
});

スクロール時に処理

イベント 内容
scroll 対象の要素がスクロールしたとき
js
window.addEventListener('scroll',()=>{
  console.log(window.scrollX,window.scrollY);
});

Intersection Observer

「基準の要素」と「監視される要素」を監視して、重なり具合をきっかけにして処理を行います。

IntersectionObserver オブジェクトの作成

js
const observer = new IntersectionObserver(callback, options);

IntersectionObserverの第一引数

交差割合が変わったときに実行されるコールバック関数です。

js
const callback = (entries, observer) => {
  entries.forEach(entry => {
    //処理
  })
}
引数 説明
entries 交差オブジェクト
observe メソッドを複数回呼び出せば複数の要素を監視できるので配列
このオブジェクトから交差の割合などの情報を引き出せる
observer IntersectionObserverオブジェクト

IntersectionObserverの第二引数

オプションです。

js
 const options = {
   root: null,
   rootMargin: 0,
   threshold: [0.25, 0.5]
 }
項目 説明
root 基準にしたい要素を指定
デフォルトはビューポート
rootMargin 対象要素との交差位置の調整
正の値を指定:画面に入る前に交差判定
デフォルト:0
threshold コールバックを実行する交差の閾値
数値か、複数の場合は配列で指定可能
例:threshold: [0.2, 0.5]の場合は、20%と50%のときに判定が行われる
範囲:0~1の間(デフォルト0)

observerのobserveメソッドで監視を開始

js
const target = document.querySelector('#something');
observer.observe(target)
IntersectionObserverEntryのプロパティ 内容
intersectionRatio 交差している領域の割合
boundingClientRect 監視対象の要素のサイズと、そのビューポートに対する位置
intersectionRect 交差している領域のサイズと、そのビューポートに対する位置
isIntersecting trueは交差状態への移行、falseは非交差状態への移行
rootBounds rootの要素のサイズと、そのビューポートに対する位置
target 監視対象
time タイムスタンプ
html
<div class="greenSection"></div>

<div class="redSection"></div>

<div class="yellowSection"></div>

<div class="greenSection"></div>

<div class="redSection"></div>

<div class=" yellowSection"></div>
css
.greenSection {
  background-color: green;
  height: 1000px;
}

.redSection {
  background-color: red;
  transition: background-color 0.4s ease-out;
  height: 2000px;
}

.redSection.active {
  background-color: blue;
}

.yellowSection {
  background-color: yellow;
  height: 1000px;
}
js
//Intersection Observerの処理
const startIntersectionObserver = () => {

  //thresholdで指定した値の時に呼ばれるコールバック
  const callback = (entries, observer) => {
    entries.forEach(entry => {

      //交差判定の時点でintersectionRationが20%以上のとき
      if (entry.intersectionRatio >= 0.2) {

        //activeクラスを追加
        entry.target.classList.add('active')
      }

      //交差判定の時点で見えている領域が20%を下回ったとき
      else if (!entry.isIntersecting) {
        //activeクラスを削除
        entry.target.classList.remove('active')
      }
    })
  }

  //Intersection Observerのオプションの指定
  const option = {

    //基準となる要素の指定(ブラウザ)
    root: null,

    //対象要素との交差位置の調整
    rootMargin: "0px",

    //20%と100%で交差判定を出す
    threshold: [0.2, 1]

    }

  //Intersection Observerのインスタンス生成
  const observer = new IntersectionObserver(callback, option);

  //対象の要素取得(NodeList)
  const targetList = document.querySelectorAll('.redSection');
  //NodeListの配列化
  const targetArray = [...targetList];

  //対象の配列の各要素を監視する
  targetArray.forEach((target) => {
    observer.observe(target);
  })

}

//Intersection Observerの処理の実行
startIntersectionObserver()

リサイズ

画面サイズ変更時に処理

スマホはスクロール時にアドレスバーが非表示になったり意図しないリサイズイベントが起こるので注意が必要です。

イベント 内容
resize ブラウザのウインドウサイズが変わった時
js
window.addEventListener('resize', () => {
  console.log('ブラウザがリサイズされた');
})

負荷削減(リサイズ時の処理削減)

サイズが1ピクセルでも変わると発火するので負荷が高いです。
リサイズの1秒後に処理を行うタイマー+リサイズごとに解除することで負荷を抑えます。

js
let resizeTimer;

window.addEventListener('resize', () => {

  //リサイズタイマーに値があればタイマー解除
  if (resizeTimer != null) {
    clearTimeout(resizeTimer);
  }

  const onResize = () => {
    console.log("リサイズした")
  }

  resizeTimer = setTimeout(() => {
    onResize();
  }, 1000);

})

window.matchMedia

違い 内容
resizeイベント ウィンドウサイズ変更ごとに実行される
matchMedia() 一度だけ実行される(負荷軽減)
メソッド 内容
matchMedia(メディアクエリ) メディアクエリの情報
matchMedia(メディアクエリ).addListener(処理) メディアクエリに一致したとき処理を実行
プロパティ 内容
matchMedia(メディアクエリ).match メディアクエリに一致するかどうか(真偽値)
js
const mediaQueryList = matchMedia('(min-width:500px)');
console.log(mediaQueryList); //幅が500以上のときtrue
js
matchMedia('(max-width:300px)').matches; //真偽値
matchMedia('(min-width:100px) and (max-width:700px)').matches; //真偽値

スマホ、タブレット、PCの3段階の例

js
document.addEventListener('DOMContentLoaded', () => {

  //スマホとタブレットの境界780px
  const mqSp2Tab = window.matchMedia('screen and (max-width: 780px)');

  //タブレットとPCの境界1280px
  const mqTab2Pc = window.matchMedia('screen and (max-width: 1280px)');

  //スマホとタブレットのブレイクポイントを通過したらイベント発火する機能
  const checkSp2Tab = (mqSp2Tab) => {
    if (mqSp2Tab.matches) {
      console.log("タブ→スマホ");
      } else {
      console.log("スマホ→タブ");
      }
    }

  //タブレットとPCのブレイクポイントを通過したらイベント発火する機能
  const checkTab2Pc = (mqTab2Pc) => {
  if (mqTab2Pc.matches) {
    console.log("PC→タブ");
    } else {
    console.log("タブ→PC");
    }
  }

  //ブレイクポイント通過したら発火する機能をイベントリスナーに追加
  mqSp2Tab.addListener(checkSp2Tab);
  mqTab2Pc.addListener(checkTab2Pc);

});

端末の縦横の向きの変更(縦横比どちらが長いかだけで判定)を感知して発火

js
document.addEventListener('DOMContentLoaded', () => {

  const mqRotation = window.matchMedia('(orientation: portrait)');

  //縦横のどちらが長いかが変わった時にイベント発火する機能
  const orientationChange = (mqRotation) => {
    if (mqRotation.matches) {
      console.log("横長→縦長");
    } else {
      console.log("縦長→横長");
    }
  }

  //縦横のどちらが長いかが変わった時にイベント発火する機能をイベントリスナーに追加
  mqRotation.addListener(orientationChange);

});

ResizeObserver

指定した監視要素のリサイズを検知して処理を行う事ができます。

observerの作成

js
const resizeObserver = new ResizeObserver(entries => {
  for (const entry of entries) { ... }
}); 

対象の監視開始

js
resizeObserver.observe(target);

対象の監視終了

js
resizeObserver.unobserve(target);

サンプル

html
<p id="text--red">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam, autem optio qui molestias accusamus fugiat nam debitis sunt modi! Voluptatum ullam minus eius earum voluptate dicta dolore neque, voluptatibus itaque maiores hic necessitatibus debitis repudiandae! Quaerat voluptatibus velit magnam veritatis, adipisci perspiciatis reprehenderit consectetur magni laboriosam, dolore repellendus doloribus nesciunt, est dolorum dignissimos. Earum, aliquam nulla? Dolor quo ipsum omnis neque obcaecati corporis sint. Libero repellendus sed autem optio quia doloribus fugiat veritatis excepturi, molestias deserunt nam facere. Quibusdam amet distinctio officia suscipit labore magnam obcaecati aperiam dolorem beatae impedit vitae optio nostrum alias voluptas repellendus similique ad corrupti commodi libero temporibus, excepturi quam. Vero ducimus nisi eaque doloremque atque facilis, tempora accusantium rerum nesciunt, aliquid asperiores ad in pariatur saepe, doloribus deleniti culpa repellendus ratione quia quisquam deserunt iste enim? Nulla, omnis. Maxime cupiditate, delectus corporis praesentium aut commodi architecto eius eos, animi odio dolore fuga totam sint. Suscipit dolorem culpa voluptatem dolor sint. Dignissimos aliquid obcaecati nemo nesciunt eos sint accusamus quos, odit neque architecto aliquam natus magni a nisi libero, omnis quas impedit voluptate! Obcaecati ducimus atque reiciendis, mollitia sint eligendi labore impedit voluptates laboriosam dolor debitis veritatis saepe amet accusamus cupiditate eum dolorem magni nulla. Esse, voluptate, dolorem inventore mollitia delectus tenetur deleniti quibusdam ducimus perspiciatis consequuntur fuga consectetur rerum itaque laboriosam nisi a maxime rem reprehenderit, qui hic enim nihil accusamus soluta! Sapiente veritatis eos obcaecati, accusamus reiciendis delectus voluptates natus odit dolor excepturi officiis iure? Saepe in tenetur soluta libero? Nam, iusto quisquam. Reiciendis, blanditiis dolor? Unde mollitia perferendis aliquid expedita sapiente, ipsa harum possimus debitis numquam exercitationem ipsam suscipit nemo porro hic dolor dolorem adipisci laudantium voluptatem fugit aliquam, tenetur voluptatibus maxime inventore! Sequi magni ad, veritatis, voluptatem aliquid dolorum mollitia laudantium libero commodi ipsam ullam porro doloremque fugit in hic quasi ut.
</p>

<p id="text--blue">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Hic, officiis nobis dolorum sint asperiores a aperiam velit fugit aliquid nihil sunt ducimus nostrum! Quas assumenda, corporis rerum molestiae in culpa! Neque dolores at consectetur nisi inventore dolorem corrupti quibusdam maxime unde eos! Ipsam asperiores consectetur unde commodi tempora sit magni ea nesciunt modi explicabo quo voluptas, provident reiciendis. Eligendi deleniti at officia excepturi nemo quae doloribus itaque, reiciendis, explicabo velit rem et cum. Laudantium pariatur doloribus ab delectus qui perspiciatis alias blanditiis vero voluptate quam dicta culpa incidunt quae illo, cupiditate praesentium fugit quia tenetur fugiat iusto ipsa nisi consequuntur. Doloribus quisquam amet fuga? At ab magnam inventore architecto nostrum explicabo quasi labore sit illum delectus, mollitia voluptatum quibusdam temporibus commodi rerum placeat? Vel quidem recusandae ipsum asperiores ratione odit porro repudiandae reiciendis doloribus ad, corporis illo est animi iste natus! Odit similique reiciendis id temporibus molestiae libero est iste, quibusdam ea! Atque, dolores? Atque minima impedit dolores nobis dolorem exercitationem officia harum? Ullam voluptatum modi sint. Voluptas numquam saepe officiis hic dolore nisi doloremque consectetur quidem, voluptatem unde ad alias vitae modi labore aut quia sit ipsa fugit perferendis inventore praesentium repellendus! Atque culpa sapiente laboriosam labore tenetur molestias velit, explicabo minima temporibus ab ad assumenda repudiandae ipsum totam aspernatur iure iste modi, amet, sint ratione placeat sed dolorum dicta. Illum magnam veniam accusantium nulla unde non esse, provident perspiciatis commodi dolorem minima doloribus fugit nihil ut qui similique laboriosam culpa earum dicta at fuga voluptas quo porro. Odio laboriosam illum, obcaecati quo aliquam hic eligendi fugit repellat earum veniam saepe iste quidem cum facilis. Saepe qui obcaecati nisi sapiente porro dolore molestias tenetur expedita explicabo, modi recusandae eos necessitatibus ipsam deleniti odit ut distinctio doloremque numquam dicta reiciendis, debitis dolorum adipisci est odio. Incidunt fugiat illo porro asperiores.
</p>

<p id="text--yellow">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Modi corporis quisquam quidem, nobis laboriosam, quasi optio delectus autem tempora accusantium eligendi, placeat magni mollitia illum est tenetur accusamus dolorem nemo! Reprehenderit, doloremque. Quos, officia corrupti fugiat nulla illum iusto accusantium ex nam necessitatibus esse dolor veniam excepturi id accusamus error impedit sed sunt quidem inventore adipisci optio quaerat rerum! Mollitia perferendis quibusdam sed impedit quo aut facere nemo quaerat odit ut pariatur cumque nulla molestiae fugiat, sunt quasi suscipit omnis consequuntur quia eligendi eaque architecto fuga maiores? Facilis dolores libero magni nam sed assumenda minus accusantium, placeat, vero aliquam inventore consequatur eligendi totam blanditiis error natus quos excepturi necessitatibus porro repellat, aspernatur soluta. Illum a, nesciunt incidunt expedita excepturi cumque autem nobis placeat impedit deleniti fugit. Optio distinctio natus dolor voluptates quaerat atque reiciendis a sit, molestiae itaque quo, sequi assumenda, quis aliquam? Nobis velit beatae magni doloremque accusamus eos aperiam quaerat amet, suscipit, error quos, culpa quod eum sunt soluta. Aspernatur repellendus eum nam eligendi, maxime repudiandae similique accusamus amet earum facilis quod explicabo fugit totam praesentium pariatur suscipit, quibusdam aperiam omnis labore adipisci sequi dolore ducimus perspiciatis! Unde explicabo vel cumque impedit recusandae. Corrupti, repellendus exercitationem? Natus iure facilis, id delectus veniam autem enim reiciendis quod voluptas, illo minima repudiandae alias dolores voluptates accusantium? Veritatis, praesentium atque? Aliquid at atque dolores quaerat, consequuntur laboriosam quae maxime maiores consectetur, quibusdam inventore beatae porro corporis fuga in ipsam mollitia velit voluptatum nisi praesentium? Consequuntur error aliquam temporibus, fugit assumenda minima aspernatur, amet natus ipsa perspiciatis aperiam sapiente voluptate incidunt atque itaque alias voluptas et neque, sit consequatur autem! Culpa dolor facere architecto consectetur nulla unde ea obcaecati dolorem neque quia sapiente provident voluptate soluta minus, eaque aperiam nihil totam sunt quas inventore adipisci debitis! Quos expedita nulla fugit tempore optio!
</p>
css
#text--red {
   background-color: red;
}

#text--blue {
  background-color: blue;
  max-width: 800px;
}

#text--yellow {
  background-color: yellow;
  max-width: 400px;
}
js

//監視対象の取得
const red = document.querySelector("#text--red");
const blue = document.querySelector("#text--blue");
const yellow = document.querySelector("#text--yellow");

const resizeObserver = new ResizeObserver(entries => {
  for (const entry of entries) {

    //リサイズされた検知した要素
    console.log("リサイズされた要素:" + entry.target.id);

    //位置、サイズ
    console.log("top:" + entry.contentRect.top);
    console.log("left:" + entry.contentRect.left);
    console.log("width:" + entry.contentRect.width);
    console.log("height:" + entry.contentRect.height);

  }

});

resizeObserver.observe(red);
resizeObserver.observe(blue);
resizeObserver.observe(yellow);

フルスクリーン表示

html
<button id="js-fullscreenButton">フルスクリーン</button>
<button id="js-clearfullscreenButton">フルスクリーン解除</button>
JavaScript
const fullscreenButton = document.getElementById("js-fullscreenButton");

fullscreenButton.addEventListener("click", (element) => {

  //chrome.firefox
  if (document.body.requestFullscreen) {
    document.body.requestFullscreen();
  }

  //edge
  if (document.body.webkitRequestFullscreen) {
    document.body.webkitRequestFullscreen();
  }

});

const clearfullscreenButton = document.getElementById("js-clearfullscreenButton");

clearfullscreenButton.addEventListener("click", () => {

  //chrome,firefox
  if (document.exitFullscreen) {
    document.exitFullscreen();
  }

  //edge
  if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen();
  }
});

ページ遷移

ページを移動

html
<button id="js-movePageButton">ページ移動</button>
JavaScript
const movePageButton = document.getElementById("js-movePageButton");

movePageButton.addEventListener("click", () => {
  location.href = "https://www.google.co.jp/";
});

ページをリロード

html
<button id="js-reloadPageButton">ページリロード</button>
JavaScript
const reloadPageButton = document.getElementById("js-reloadPageButton");

reloadPageButton.addEventListener("click", () => {

  //キャッシュを使ったリロード
  location.reload();

  //キャッシュを使わないリロード
  location.reload(true);

});   

別ウィンドウを開く

html
<button id="js-openNewWindowButton">別ウィンドウを開く</button>
JavaScript
const openNewWindowButton = document.getElementById("js-openNewWindowButton");

openNewWindowButton.addEventListener("click", () => {
  window.open("https://www.google.co.jp");
});

履歴

履歴の前後のページに移動

メソッド 内容
history.back() 履歴1つ戻る
history.forward() 履歴1つ進む
history.go(n) 履歴nつ進む
html
<button id="js-historygoMinus2Button">履歴2つ戻る</button>
<button id="js-historyBackButton">履歴1つ戻る</button>
<button id="js-historyforwardButton">履歴1つ進む</button>
<button id="js-historygo2Button">履歴2つ進む</button>
JavaScript
const historygoMinus2Button = document.getElementById("js-historygoMinus2Button");
const historyBackButton = document.getElementById("js-historyBackButton");
const historyforwardButton = document.getElementById("js-historyforwardButton");
const historygo2Button = document.getElementById("js-historygo2Button");

historygoMinus2Button.addEventListener("click", () => {
  history.go(-2);
});

historyBackButton.addEventListener("click", () => {
  history.back();
});

historyforwardButton.addEventListener("click", () => {
  history.forward();
});

historygo2Button.addEventListener("click", () => {
  history.go(2);
});

履歴の操作

URLが変わるだけで、ページの内容は変わりません。
実際にそのページに遷移する場合は読み込み処理を書く必要があります。

html
const historyPushButton = document.getElementById("js-historyPushButton");
const historyReplaceButton = document.getElementById("js-historyReplaceButton");
const historyBackButton = document.getElementById("js-historyBackButton");
const historyforwardButton = document.getElementById("js-historyforwardButton");
JavaScript
const historyPushButton = document.getElementById("js-historyPushButton");
const historyReplaceButton = document.getElementById("js-historyReplaceButton");
const historyBackButton = document.getElementById("js-historyBackButton");
const historyforwardButton = document.getElementById("js-historyforwardButton");

historyPushButton.addEventListener("click", () => {
  history.pushState(null, null, "/nextpage.html");
  console.log("履歴が追加された");
});

historyReplaceButton.addEventListener("click", () => {
  history.replaceState(null, null, "/other.html");
  console.log("履歴が上書きされた");
});

historyBackButton.addEventListener("click", () => {
  history.back();
});

historyforwardButton.addEventListener("click", () => {
  history.forward();
});

window.addEventListener("popstate", () => {
  console.log("履歴が辿られた");
})
24
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
25