애플 사이트 만들기 2 - 헤더 만들기 2

프망생·2025년 5월 4일

웹사이트 만들기

목록 보기
9/10

검색 창 만들기

<div class="search-wrap">
        <div class="search">
          <div class="shadow"></div>
          <div class="textfield">
            <input type="text" placeholder="apple.com 검색">
            <div class="search-icon"></div>
            <div class="search-closer"></div>
          </div>
          <div class="autocompletes">
            <h3>빠른 링크</h3>
            <ul>
              <li><a href="javascript:void(0)">Apple Store Online에서 쇼핑하기</a></li>
              <li><a href="javascript:void(0)">Apple Visoin Pro</a></li>
              <li><a href="javascript:void(0)">Airpods</a></li>
              <li><a href="javascript:void(0)">Apple Intelligence</a></li>
              <li><a href="javascript:void(0)">Apple Trade In</a></li>
            </ul>
          </div>
        </div>
      </div>
header .search-wrap {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  visibility: hidden;
  opacity: 0;
  transition: 0.4s;
}

header .search {
  max-width: 680px;
  margin: 0 auto;
  position: relative;
}

header .search .shadow {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
}

header .search .textfield {
  position: relative;
}

header .search input {
  width: 100%;
  height: 44px;
  padding: 0 40px;
  border: none;
  outline: none;
  box-sizing: border-box;
  background-color: transparent;
  font-size: 17px;
  color: #FFF;
}

header .search .search-icon  {
  width: 40px;
  height: 44px;
  background-image: url("../images/header_search.svg");
  background-repeat: no-repeat;
  background-position: center;
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0.4;
}

header .search .search-closer {
  width: 40px;
  height: 44px;
  background-image: url("../images/header_close.svg");
  background-repeat: no-repeat;
  background-position: center;
  position: absolute;
  top: 0;
  right: 0;
  opacity: 0.4;
  cursor: pointer;
}

header .search .search-closer:hover {
  opacity: 1;
}

header .search .autocompletes {
  width: 100%;
  padding: 26px 40px 20px;
  border-radius: 0 0 18px 18px;
  box-sizing: border-box;
  background-color: #FFF;
  position: absolute;
  top: 44px;
  left: 0;
}

header .search .autocompletes h3 {
  font-size: 12px;
  color: #6E6E6E;
  margin-bottom: 12px;
}

header .search .autocompletes ul li a {
  display: block;
  margin: 0 -14px;
  padding: 10px 0 10px 30px;
  font-size: 14px;
  cursor: pointer;
}

header .search .autocompletes ul li a:hover {
  background-color: #F5F5F5;
  font-weight: 500;
}

header .search input,
header .search .search-icon,
header .search .autocompletes h3,
header .search .autocompletes li {
  transition: 0.6s;
  transform: translate(100px, 0);
}

header.searching ul.menu > li {
  transform: scale(0.7);
  opacity: 0;
}

header.searching .search-wrap {
  visibility: visible;
  opacity: 1;
  transition-delay: 0.2s;
}

header.searching .search input,
header.searching .search .search-icon,
header.searching .search .autocompletes h3,
header.searching .search .autocompletes li {
  transform: translate(0, 0);
  transition-delay: 0.2s;
}
const headerEl = document.querySelector('header')
const headerMenuEls = [...headerEl.querySelectorAll('ul.menu > li')]
const searchWrapEl = headerEl.querySelector('.search-wrap')
const searchStartEl = headerEl.querySelector('.search-starter')
const searchCloserEl = searchWrapEl.querySelector('.search-closer')
const searchSchadowEl = searchWrapEl.querySelector('.shadow')
const searchInputRl = searchWrapEl.querySelector('input')
const searchDelayEls = [...searchWrapEl.querySelectorAll('li')]

searchStartEl.addEventListener('click', showSearch)

searchCloserEl.addEventListener('click', hideSearch)

searchSchadowEl.addEventListener('click', hideSearch)

function showSearch() {
  headerEl.classList.add('searching')
  document.documentElement.classList.add('fixed')
}

function hideSearch() {
  headerEl.classList.remove('searching')
  document.documentElement.classList.remove('fixed')
}

검색 바 등장 시 스크롤 방지

html.fixed {
  position: fixed;
  overflow-y: scroll;
  width: 100%;
}
function showSearch() {
  headerEl.classList.add('searching')
  document.documentElement.classList.add('fixed')
}

function hideSearch() {
  headerEl.classList.remove('searching')
  document.documentElement.classList.remove('fixed')
}

위 코드에서는 showSearch() 함수를 통해 검색바가 보이면 html에 fixed 클래스를 추가하고 hideSearch() 함수를 통해 검색바가 없어지면 html에 fixed 클래스를 제거한다.
이때 html에 fixed 클래스가 추가되었다면 css에서 position: fixed; 에 의해 html은 현재 위치에 고정되게 되고 overflow-y: scroll에 의해 y축(높이)에는 스크롤이 등장하게된다. 하지만 html은 현재 위치에 고정되어 있지 때문에 스크롤을 할 수 없는 상태가 된다.

검색 바 애니메이션


검색 버튼을 터치하였을 경우 메뉴 버튼이 우측에서부터 사라지고 검색 바가 상단에서부터 출력되고 있다.

header .search input,
header .search .search-icon,
header .search .autocompletes h3,
header .search .autocompletes li {
  transition: 0.6s;
  transform: translate(100px, 0);
}

위 코드를 통해 검색 바는 기본적으로 x축 기준으로 100px만큼 우측으로 이동해있는 상태이다.

function showSearch() {
  headerEl.classList.add('searching')
  document.documentElement.classList.add('fixed')
  headerMenuEls.reverse().forEach(function(el, index) {
    el.style.transitionDelay = index * 0.4 / headerMenuEls.length + 's'
  })
  searchDelayEls.forEach(function (el, index) {
    el.style.transitionDelay = index * 0.4 / searchDelayEls.length + 's'
  })
  setTimeout(function(){
    searchInputRl.focus()
  },600)
}

만약 검색 버튼을 누르면 showSearch() 함수가 실행되고 css의 header의 클래스에 searching이 추가된다.

header.searching ul.menu > li {
  transform: scale(0.7);
  opacity: 0;
}

header.searching .search-wrap {
  visibility: visible;
  opacity: 1;
  transition-delay: 0.2s;
}

header.searching .search input,
header.searching .search .search-icon,
header.searching .search .autocompletes h3,
header.searching .search .autocompletes li {
  transform: translate(0, 0);
  transition-delay: 0.2s;
}

header에 searching이 추가되면서 검색창에 스타일이 변화가 생기게 되는데, opacity: 1; 보이지 않던 검색 바가 보이게 되고 transform: translate(0, 0);를 통해 x축으로 이동해 있던 검색 바가 원위치로 이동하게 된다.

function showSearch() {
 headerMenuEls.reverse().forEach(function(el, index) {
    el.style.transitionDelay = index * 0.4 / headerMenuEls.length + 's'
  })
  searchDelayEls.forEach(function (el, index) {
    el.style.transitionDelay = index * 0.4 / searchDelayEls.length + 's'
  })
}

function hideSearch() { 
 headerMenuEls.reverse().forEach(function(el, index) {
    el.style.transitionDelay = index * 0.4 / headerMenuEls.length + 's'
  })
  searchDelayEls.reverse().forEach(function (el, index) {
    el.style.transitionDelay = index * 0.4 / searchDelayEls.length + 's'
  })
  searchDelayEls.reverse()
  
}

transitionDelay 함수는 CSS 전환이 시작되기 전까지의 지연 시간을 나타낸다.
페이지에서는 검색창 등장 시 기존 메뉴가 한개씩 사라지고 검색바의 검색어가 하나씩 등장하는 효과가 보여지고 있다. 이 경우에는 배열을 이용할 필요가 있다.
const searchDelayEls = [...searchWrapEl.querySelectorAll('li')]
searchWrapEl 안의 모든 li요소를 사용하는데 전개 연산자 [...]를 사용하여 해당 요소들을 배열로 사용할 수 있다.

searchDelayEls.reverse().forEach를 사용하여 배열의 각 요소에 transitionDelay를 지정할 수 있다.
el.style.transitionDelay = index * 0.4 / searchDelayEls.length + 's' 함수를 통해 각 배열 별로 계산을 하여 검색창이 1개씩 등장하는 느낌을 줄 수 있게된다.
ex) 해당 페이지의 searchDelayEls.length 값은 12이다.
첫번째 배열의 경우 index = 0이다. 그렇기에 첫번째 배열은 0 0.4 / 12 +'s' 이기에 0s이다.
두번째 배역의경우 index = 1이다. 그렇기에 첫번째 배열은 1
0.4 / 12 +'s' 이기에 0.0333s이다.

searchDelayEls.reverse().forEach 에서 reverse() 사용 이유?

검색 버튼을 터치하였을 경우 메뉴 버튼이 우측에서부터 사라지고 검색 바가 상단에서부터 출력되고 있다.
만약 reverse() 사용하지 않았다면 검색 버튼 터치 시 메뉴 버튼은 우측부터가 아닌 좌측에서부터 사라지게 될 것이다.
마찬가지로 검색 바가 사라졌을 때 검색 바가 하단에서부터 사라지고 메뉴 버튼은 좌측에서부터 등장하고 있다.
검색 바가 없어졌을 때의 함수에 reverse()를 사용함으로써 검색 바가 사라졌을 경우 상단이 아닌 하단부터 사라지고 메뉴버튼이 좌측에서부터 등장하도록 하고 있다.

검색 바 포커싱


검색 버튼을 터치했을 경우 자동으로 검색 바에 포커싱이 되도록 구성할 것이다.

const headerEl = document.querySelector('header')
const searchWrapEl = headerEl.querySelector('.search-wrap')
const searchInputRl = searchWrapEl.querySelector('input')


function showSearch() {
  headerEl.classList.add('searching')
  document.documentElement.classList.add('fixed')
  headerMenuEls.reverse().forEach(function(el, index) {
    el.style.transitionDelay = index * 0.4 / headerMenuEls.length + 's'
  })
  searchDelayEls.forEach(function (el, index) {
    el.style.transitionDelay = index * 0.4 / searchDelayEls.length + 's'
  })
  setTimeout(function(){
    searchInputRl.focus()
  },600)
}

function hideSearch() {
  headerEl.classList.remove('searching')
  document.documentElement.classList.remove('fixed')
  headerMenuEls.reverse().forEach(function(el, index) {
    el.style.transitionDelay = index * 0.4 / headerMenuEls.length + 's'
  })
  searchDelayEls.reverse().forEach(function (el, index) {
    el.style.transitionDelay = index * 0.4 / searchDelayEls.length + 's'
  })
  searchDelayEls.reverse()
  setTimeout(function(){
    searchInputRl.value = ''
  },600)
  
}

위 코드에서 searchInputRl.focus()을 통해 해당 창에 포커싱하도록 할 수 있다.
하지만 위 코드에서는 버튼을 터치하였을 경우 0.4s 뒤에 검색창이 출력되기에 포커싱이 되지 않는 오류가 생긴다.
이 경우에는 setTimeoutset 함수를 사용하면된다.

setTimeoutset 함수는 setTimeoutset(함수(), 시간(ms)) 형식으로 사용하는데 특정 시간 뒤에 함수() 영역이 실행된다고 할 수 있다.

setTimeout(function(){
    searchInputRl.focus()
  },600)

위 코드에서는 0.6초 뒤에 searchInputRl.focus() 함수가 실행되어 검색 창에 포커싱이 되도록 할 수 있다.

또한 검색 창을 끄고 닫으면 이전에 검색했던 단어가 남아있는 오류가 생길 수 있다.
이럴경우 검색창을 닫는 함수 뒤에 searchInputRl.value = ''을 추가하여 입력한 값을 초기화 해줄 수 있다.

profile
안녕하세요. 프론트엔드 지망생입니다.

0개의 댓글