iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Modern Web

給前端新手的圖文故事系列 第 15

學習如何操作 DOM 與異步概念

  • 分享至 

  • xImage
  •  

The HTML DOM (Document Object Model) 操作

下方是 W3CShool 所提供的圖,他簡單跟我們頗析了網頁的結構,就跟我們之前的課程所提過的一樣,Web 本質上就是一個巨大的 Object,因此操作 DOM 的意思,其實就是用程式運行的方式,動態控制著這個網頁所發生的一切。
The HTML DOM Tree of Objects

修改 DOM 中的 title

  <script>
    document.title = '我是新的標籤';
  </script>

在上方的案例中,我們很輕鬆的改變了網站上的 title,doucment 作為我們文件的主體,是很常拿來進行操作的物件,大家不妨用 console.log(document),來查看這個物件裡面有什麼可以改吧!

簡單的操作範例

  <body>
    <button onclick="myFunction()">點擊我</button>

    <div id="myDiv">This is my DIV element.</div>
  </body>
  <script>
    // 取得物件實體
    var myDiv = document.getElementById('myDiv');
    
    // 可以看到這個實體內的「文字內容」
    console.log(myDiv.textContent);

    // 建立一個用於互動的函示
    function myFunction() {
      if (myDiv.style.display === 'none') {
        myDiv.style.display = 'block';
      } else {
        myDiv.style.display = 'none';
      }
    }
    
    // 改個寫法並用三元運算式
    //  const myFunction = function(){
    //      myDiv.style.display === 'none'
    //        ? (myDiv.style.display = 'block')
    //        : (myDiv.style.display = 'none');
    //  }
  </script>

加上樣式的 DOM 操作

從上面的練習中我們不難看出,實際上 JavaScript 在操作 DOM 時是相對直覺的,但網頁設計終究不是一個單體,這次就讓我們搭配起 CSS 來呈現畫面吧!


  <style>
    .height-line {
      border-bottom: 1px solid #ffccee;
    }
  </style>
  
  <body>
    <button onclick="addColor()">點擊我修改顏色</button>
    <button onclick="addClass()">點擊我新增樣式類別</button>

    <div id="myDiv">This is my DIV element.</div>
    <span id="mySpan">This is my SPAN element.</span>
  </body>
  
  <script>
    // 建立實體物件
    var myDiv = document.getElementById('myDiv');
    var mySpan = document.getElementById('mySpan');

    // 新增顏色功能
    var addColor = function () {
      myDiv.style.color = 'red';
    };

    // 新增樣式功能
    var addClass = function () {
      myDiv.className = 'height-line';
      mySpan.className = 'height-line';
    };
    
    // 帶入變數的顏色修改
	// var addColor = function (color) {
    //   myDiv.style.color = color;
    // };
  </script>

上方的範例也可以帶入多個變數進行複雜的操作,例如像下面那樣給複數的參數,先動態建立目標實體後,在進行額外的操作。

小叮嚀: 物件的 style 屬性可以操作 CSS 的大部分特性

var changeTargetColor = function (target, color) {
    var targetElement = document.getElementById(target);
    targetElement.style.color = color;
};

changeTargetColor('mySpan','blue')

上面的範例基本上均為練習操作,而現在我們已經擁有足夠的練習量,是時候該來製作一個常用 UI 出來了!

作為 DOM 基礎練習的尾聲,我們來做一個標籤切換吧!

在實作之前,我們先思考一下我們目標是什麼,這是寫程式開發非常重要的一環,因此等等在實作的過程中,我們也會將 HTML、CSS、JavaScript 的部分先進行分離,首先讓我們開始思考,我們要結構要長得的怎麼樣吧!

  <body>
    <article>
      <ol>
        <li>標籤一</li>
        <li>標籤二</li>
        <li>標籤三</li>
      </ol>
      <section>標籤項目一</section>
      <section>標籤項目二</section>
      <section>標籤項目三</section>
    </article>
  </body>

符合大家的預期嗎?這邊的設計是思考說作為一個可以切換的功能,應該要有一個包覆在最外面的主層,而子項目會有兩個分支,一個是標籤的按鈕列,另外一個是內容的呈現頁,當然這裡也可以做一層額外的包覆,但這類型本身就是見仁見智的設計方式.

接下來讓我們思考一下樣式的問題吧!儘管這麼說,但可惜的是我們是以 JavaScript 為主的課程,不過下方的樣式還是能大概說明一下的,我們首先對 Body 做了定義,但這只是為了讓之後的內容置中好查看而已,之後就是對 tab-box 本身進行設計,還另外設計了當 tab 被點擊時會產生的效果,另外各位同學也要記得,要把這些樣式加到我們的 HTML 上才有用喔!

    <style>
      body {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
      }
      .tab-box {
        display: flex;
        flex-direction: column;
        width: 400px;
      }

      .tab-box section {
        padding: 1rem;
        min-height: 5rem;
        background-color: bisque;
      }

      .tab-box__header {
        display: flex;
        padding-left: 0;
        list-style: none;
      }

      .tab-box__header > li {
        margin-right: 10px;
      }

      .tab-box__header > li.active {
        color: blueviolet;
      }
    </style>
    
    <body>
    <article class="tab-box">
      <ol class="tab-box__header">
      ...

終於來到最後的重頭戲了,為了方便學習我們會設計兩個版本出來,一個是以上面作為基礎延伸的版本,另外一個是藉由 「程式」 特性實作的版本,那就先嚷我們從簡單的開始吧!

    <style>
      body {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
      }
      .tab-box {
        display: flex;
        flex-direction: column;
        width: 400px;
      }

      .tab-box section {
        padding: 1rem;
        min-height: 5rem;
        background-color: bisque;
      }

      .tab-box__header {
        display: flex;
        padding-left: 0;
        list-style: none;
      }

      .tab-box__header > li {
        margin-right: 10px;
        cursor: pointer;
      }

      .tab-box__header > li.active {
        color: blueviolet;
      }
    </style>
  </head>
  <body>
    <article class="tab-box" id="tab-box">
      <ol class="tab-box__header">
        <li onclick="triggerSection(event,'targetOne')" id="defaultOpen">
          標籤一
        </li>
        <li onclick="triggerSection(event,'targetTwo')">標籤二</li>
        <li onclick="triggerSection(event,'targetThree')">標籤三</li>
      </ol>
      <section id="targetOne">標籤項目一</section>
      <section id="targetTwo">標籤項目二</section>
      <section id="targetThree">標籤項目三</section>
    </article>
  </body>
  <script>
    // 取得全部需要使用的 UI
    var tabBox = document.getElementById('tab-box');
    var tabBoxTab = document.querySelectorAll('li');
    var tabBoxSection = document.querySelectorAll('section');

    var triggerSection = function (event, target) {
      // 消除全部的 Section 顯示
      tabBoxSection.forEach(function (element) {
        element.style.display = 'none';
      });

      // 取得目標實體
      var targetElement = document.getElementById(target);

      // 顯示目標
      targetElement.style.display = 'block';

			// 清除 tab 上的樣式並且將目前點擊的目標加上 active
      tabBoxTab.forEach(function (element) {
        element.classList = '';
      });
      event.currentTarget.className += ' active';
    };

    // 預設出發此名稱的點擊事件
    document.getElementById('defaultOpen').click();
  </script>

相對直覺的程式對吧!除了使用 forEach 來處理 DOM 元素以外,基本上都是我們老生常談的東西了,當然當中也有像是點擊事件傳入,以及預設點擊操作這種行為,但其實都還算好理解的範圍,但現今這樣做其實是有弊端的,各位可以看到我們與 DOM 本身的牽扯性極強,包括需要給予很多 ID 這點,對於一個可重複利用與需要額外擴充的 UI 都非常不良,因此下面就讓我們用程式的思維,來解決這過於土法煉鋼的問題吧!


  <style>
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
    }
    .tab-box {
      display: flex;
      flex-direction: column;
      width: 400px;
    }

    .tab-box section {
      padding: 1rem;
      min-height: 5rem;
      background-color: bisque;
    }

    .tab-box__header {
      display: flex;
      padding-left: 0;
      list-style: none;
    }

    .tab-box__header > li {
      margin-right: 10px;
      cursor: pointer;
    }

    .tab-box__header > li.active {
      color: blueviolet;
    }
  </style>
  <body>
    <article class="tab-box" id="tab-box">
      <ol class="tab-box__header">
        <li>標籤一</li>
        <li>標籤二</li>
        <li>標籤三</li>
      </ol>
      <section>標籤項目一</section>
      <section>標籤項目二</section>
      <section>標籤項目三</section>
    </article>
  </body>
  <script>
    var tabBox = document.getElementById('tab-box');
    // 在已經搜尋出的 DOM 物件內在進行搜尋
    var tabBoxTab = tabBox.querySelectorAll('li');
    var tabBoxSection = tabBox.querySelectorAll('section');

    var triggerTab = function (tabNumber) {
      tabBoxSection.forEach(function (element, index) {
        element.style.display = 'none';
        // 使用 forEach 自帶的 index,同時對 tabBoxTab 進行同樣的處理
        tabBoxTab[index].className = '';
      });
      tabBoxSection[tabNumber].style.display = 'block';
      tabBoxTab[tabNumber].className = 'active';
    };

    tabBoxTab.forEach(function (element, index) {
      element.onclick = function () {
        triggerTab(index);
      };
    });

    triggerTab(0);
  </script>

所謂的變數以及參數,其實有著許多我們可以額外操作的細節空間,在這個範例中,我們藉由陣列的特性將原先兩次的 forEach 合併處理,並且同時處理了原先綁定在 UI 上的操作,讓程式的延展性提供,雖然以目前的結果而言都是好處居多,但其實大家應該不難看出,當我們與 HTML 進行分離的同時,整個設計其實也開始愈發抽象了。

進階討論 (倒數計時專案)

  <body>
    <p id="downDate"></p>
  </body>
  
  <script>
    // 定義倒計時目標時間
    var countDownDate = new Date('July 15, 2022 16:10:25').getTime();

    // 新增每秒更新時間的函示
    var x = setInterval(function () {
    // 取得今日的日期
    var now = new Date().getTime();

    // 找出現在和倒計時日期之間的時間
    var distance = countDownDate - now;

    //天、小時、分鐘和秒的時間計算
    var days = Math.floor(distance / (1000 * 60 * 60 * 24));
    var hours = Math.floor(
      (distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
    );
    var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
    var seconds = Math.floor((distance % (1000 * 60)) / 1000);

    // 在 id="downDate" 的元素中顯示結果
    document.getElementById('downDate').innerHTML =
      days + 'DAY ' + hours + 'h ' + minutes + 'm ' + seconds + 's ';

    // 如果倒計時結束的話,就顯示一些預設文字
    if (distance < 0) {
      clearInterval(x);
      document.getElementById('downDate').innerHTML = '倒數已結束';
    }

    // console.log(parseInt(Math.random() * 10));
    }, 1000);
  </script>

上一篇
了解 JavaScript 基礎邏輯運算
下一篇
初步了解物件陣列與物件(Arrays and Objects)
系列文
給前端新手的圖文故事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言