** 아이폰을 쓰지말자..!
각 브라우저별로 기본 스타일을 가지고있다.
따라서 iOS/Safari 에서도 일관된 스타일을 유지하기 위해 CSS 초기화가 필요하다.
이외에도, iOS에서만 가지고 있는 여러 문제들을 함께 다뤄보겠다.
CSS 스타일
1) CSS 초기화 사이트를 이용하여 기본 스타일 제거
브라우저별 스타일을 초기화 하기 위해서 리셋 스타일을 제공하는 사이트를 많이 이용한다.
보통 Normalize나 Reset이 가장 많이 사용된다.
Normalize CSS는 유용한 스타일들을 유지시키고, 버그 및 브라우저 간 차이점을 일치시켜준다.
github를 통해 지속적인 업데이트를 하고있기 때문에 Reset CSS보다 안정성이 높다.
하지만 스타일에 대한 가이드가 명확하게 없으며, 만들고자하는 스타일의 변화가 크다면 하드 리셋이 더 편리할 수 있다.
Reset CSS같은 하드 리셋 CSS들은 모든 것을 reset 하고 시작하기 때문에 고려해야할 변수가 적다.
그만큼 코드의 길이가 길어질 수 있다는 것을 감안해야한다.
하드 리셋 CSS 사이트
1. HTML5 Doctor Reset CSS
2. css-wipe
3. Reset CSS(Eric Meyer's CSS reset)
4. Tinyreset-tiny CSS reset for the modern web
이와 같이 css 초기화를 돕는 사이트를 통해 iOS의 스타일을 초기화 시킨 뒤 사용하는 것을 추천한다.
2) 필요할 경우 CSS 리셋 코드 추가 작성
위의 사이트들에 존재하지 않는다면 추가하는게 좋은 코드를 소개해보겠다.
Safari 에서 제공하는 Safari Web Content Guide에서 확인할 수 있다.
특히 -webkit- 접두사를 추가하여 iOS에 최적화된 스타일을 적용시키는 것을 추천한다.
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 0;
box-sizing: border-box;
}
input[type="button"],
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="month"] {
-webkit-appearance: none;
-webkit-border-radius: 0;
box-sizing: border-box;
}
html {
height: -webkit-fill-available;
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
input, select, button 태그의 css를 수정하기 위해서 appearance를 none으로 기본 설정하면 개발자가 정의한 스타일을 정의할 수 있도록 변경된다.
border-radius: 0; box-sizing: border-box; 설정하면 폼 요소의 크기가 예측 가능하며,레이아웃이 깨지는 것을 방지할 수 있다.
iOS 모바일 기기에서 웹사이트를 세로에서 가로로 전환할 때 기본 글꼴 크기를 늘리는 현상이 있다.
text -size-adjust 라는 속성을 통해 이를 방지할 수 있다.
-webkit-fill-available은 iOS와 크롬 브라우저에서 사용되는 CSS 속성으로, 전체높이(뷰포트의 가로공간)을 채우로도록하는 설정이다.
특히, iOS Safari에서 주소창이 숨겨지거나 나타날 때 페이지가 잘리는 문제를 해결하는데 도움을 준다.
** input과 select에 padding이 적용 안되는 경우
input, select{
line-height:
text-indent:
}
3) iOS의 vh, vw 기준이 다르다
대부분의 브라우저는 viewport 메타 태그의 설정을 정확히 따르며, 브라우저 창 크기와 뷰포트 크기가 일치한다.
하지만 iOS Safari는 고정된 논리적 뷰포트 크기를 사용하며, 사용자가 화면을 확대하거나 축소할 때 동적으로 크기를 조정한다. 이로 인해 vh, vw같은 단위가 잘못 계산되기도 하는 것이다.
(vh가 아이폰 자체 하단 메뉴 UI에 덮어 씌워지는 경우도 발생)
해결 방법은 vh 대신 height: auto 나 100%를 사용하거나 높이를 동적으로 계산하여 업데이트하면 된다.
css 커스텀 프로퍼티로 동적 계산
:root { --vh: 100%; } html, body { height: var(--vh); }
JavaScript로 동적 vh 업데이트
const setVh = () => { document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`); }; window.addEventListener('resize', setVh); setVh();
4) iOS의 렌더링 특성
iOS는 safari 브라우저와 WebKit 렌더링 엔진을 사용한다.
iOS 렌더링의 주요 특성을 이해하면 문제를 예방하거나 해결하기 쉽다.
iOS Safari :
- 고정된 논리적 뷰포트 크기를 사용하고, 사용자가 화면을 확대하거나 축소할 때 동적으로 크기를 조정한다.(vh,vw 영향을 줄 수 있음)
- 터치 이벤트(touchstart, touchmove, touchend)를 우선 처리하여 터치 위치나 스크롤이 비동기적으로 처리되는 경우가 많다. 그렇기 때문에 터치 이벤트와 스크롤 값을 함께 고려해야한다.
- 스크롤 발생 시 scrollTop 값이 즉시 반영되지 않을 수 있다.
- 성능과 배터리 효율성을 최적화 하기 위해 일부 애니메이션과 렌더링을 비동기 처리한다.(z-index, transform, will-change 같은 속성이 예상치 못한 방향으로 렌더링에 영항을 줄 수있다.)
5) 오프셋 오류(offset error)
css로 인한 오프셋 오류
css로 인한 오프셋 문제일 경우 viewport 메타 태그가 설정되어있는지 확인하고, 그래도 찾지 못할 경우
모든 요소에 outline: 1px solid red;를 추가하여 디버깅하기도한다.
* { outline: 1px solid red; /* 모든 요소에 경계선을 추가 */ }
JavaScript 오프셋 계산 문제
JavaScript 에서 offsetTop, offsetLeft, getBoundingClientRect() 등을 사용할 때 잘못된 값을 반환됐다면,iOS에서 스크롤이 화면 크기에 영향을 미쳐 계산이 틀어진 걸수도 있다.
이 경우, getBoundingClientRect() 메서드를 사용하여 정확한 위치 값을 가져오면 된다.
// javascript const element = document.querySelector('.my-element'); const rect = element.getBoundingClientRect(); console.log(`x: ${rect.x}, y: ${rect.y}`); console.log(`width: ${rect.width}, height: ${rect.height}`);
아이폰에서는 스크롤 동작과 관련된 추가적인 오프셋(scrollTop, scrollLeft 등) 문제가 자주 발생한다.
이 또한 getBoundingClientRect() 메서드를 통해 스크롤 값을 더하거나 빼서 위치를 계산해야 한다.const element = document.querySelector('.my-element'); const offsetY = element.getBoundingClientRect().top + window.scrollY; const offsetX = element.getBoundingClientRect().left + window.scrollX; console.log(`정확한 x: ${offsetX}, 정확한 y: ${offsetY}`);
6) 터치 이벤트 관련 문제
터치 이벤트에서 좌표를 계산할 때 iOS 기기에서 잘못된 터치 위치를 계산하는 경우가 있다.
다른 플렛폼들은 마우스 이벤트 중심이며, 터치 이벤트는 보조적인 역할을 한다.
스크롤도 비교적 예층 가능하고, 스크롤 값 계산이 일관된다.
이에 반해, iOS Safari는 터치 이벤트(touchstart, touchmove, touchend)를 우선 처리하며,
이로 인해 터치 위치와 스크롤이 비동기적으로 처리되는 경우가 많다.
또한, iOS에서 스크롤이 발생할 경우 scrollTop값이 즉시 반영되지 않을 수 있다.(위의 오프셋 문제 참고)
이 경우 pageX, pageY 대신 clientX, clientY를 사용하면 해결되는 경우가 많다.
document.addEventListener('touchstart', (event) => { const touch = event.touches[0]; console.log(`터치 좌표 - x: ${touch.clientX}, y: ${touch.clientY}`); });
터치 이벤트와 스크롤 값을 함께 고려document.addEventListener('touchstart', (event) => { const touch = event.touches[0]; console.log(`터치 좌표: ${touch.clientX}, ${touch.clientY}`); }); window.addEventListener('scroll', () => { console.log(`스크롤 값: ${window.scrollY}`); });
7) Retina 디스플레이 문제
대부분의 브라우저는 1픽셀 = 1 CSS 픽셀로 동작하고, 저해상도 디스플레이에서 UI가 크게 다르지 않다.
하지만 아이폰은 Retina 디스플레이(고해상도 화면)를 사용하여, 1 CSS 픽셀이 실제로 여러 물리적 픽셀에 매핑된다.
이는 픽셀 밀도(HiDPI) 문제를 발생시킬 수 있다. 픽셀 밀도가 높아지면서 계산 오류가 발생하는 것이다.
이를 해결하기 위해 devicePixelRatio를 고려하여 위치를 계산하면 된다.
const scaleFactor = window.devicePixelRatio; console.log(`픽셀 비율: ${scaleFactor}`);
const scaleFactor = window.devicePixelRatio; const element = document.querySelector('.my-element'); const rect = element.getBoundingClientRect(); console.log(`x: ${rect.x * scaleFactor}, y: ${rect.y * scaleFactor}`);
8) 기타 사항
position: fixed 가 스크롤 중 예상대로 고정되지 않는 문제
overflow: scroll이 제대로 작동하지 않아 가상화면 스크롤 문제가 발생
z-index, transform, will-chage 를 활용하여 렌더링 성능 최적화
transform: translateZ(0)은 하드웨어 가속을 활성화 하여 렌더링 품질을 향상시킨다.
iOS에서 ::after와 같은 가상요소가 제대로 렌더링되지 않는 문제를 해결한다.
div { will-change: transform; /* 애니메이션 성능 최적화 */ transform: translateZ(0); /* 3D 렌더링으로 강제 GPU 가속 */ }
border로 삼각형을 만들었을 경우 깨짐 현상
width, heigth를 0으로 설정하고 border로 삼각형을 만드는 것은 같단하고 널리 사용되는 방식이지만,
렌더링 품질에 따라 렌더링 과정에서 부정확 하거나 깨진 모양으로 보일 수 있다.
그냥, svg를 가져와 사용하거나, clip-path: polygon을 통해 만드는 것을 추천한다.
div { width: 16px; /* 삼각형의 너비 */ height: 8px; /* 삼각형의 높이 */ background-color: #3c90ee; /* 삼각형의 색상 */ clip-path: polygon(50% 0, 0 100%, 100% 100%); /* 삼각형 모양 */ }
너비 16px, 높이 8px로 설정했다.
background-color삼각형에 색상을 채우기 위해 background-color를 사용한다.
clip-pathpolygon(50% 0, 0 100%, 100% 100%)는 위쪽이 뾰족한 삼각형을 생성한다.
▲ 와 같은 삼각형 모양
* 50% 0: 삼각형의 꼭대기 점(가운데 위쪽).
* 0 100%: 삼각형의 왼쪽 아래.
* 100% 100%: 삼각형의 오른쪽 아래.
▼ 와 같은 삼각형 모양
* 50% 100%: 삼각형의 아래쪽 꼭짓점 (중앙 아래).
* 0 0: 삼각형의 왼쪽 위.
* 100% 0: 삼각형의 오른쪽 위.
Viewport 설정
모바일은 모바일 뷰를 기준으로 렌더링 된다. 그렇기 때문에 html에 meta 태그로 viewport를 설정해 주는것이 필요하다.
viewport를 설정하지 않을 경우, 모바일 기기에서는 뷰포트가 일반적으로 고정너비(약 980px)로 설정되어 반응형이 적용되지 않을 수 있고, 기존 해상도(dpi) 설정과 디바이스(장치) 해상도의 차이로 인해 디바이스 화면보다 큰 픽셀의 화면을 불러들여 표현하게 될 수도있다.
대부분의 브라우저는 viewport 메타 태그의 설정을 정확히 따르며, 브라우저 창 크기와 뷰포트 크기가 일치한다.
하지만 iOS Safari는 고정된 논리적 뷰포트 크기를 사용하며, 사용자가 화면을 확대하거나 축소할 때 동적으로 크기를 조정한다. 이로 인해 vh, vw같은 단위가 잘못 계산되기도 하는 것이다.
<meta name="viewport" content="width=device-width, initial-scale=1.0">
** input 클릭 시 View가 확대되는 경우(focus zoom-in)
input 을 클릭하면 화면이 확대되고 사용자가 일일히 다시 화면을 줄여야 하는 상황이 발생할 수 있다.
안드로이드에서는 그런 경우가 거의 발생하지 않지만 iOS 에서는 input, select, textarea의 폰트 크기가 16px 보다 작은 경우 focus시 자동으로 확대되도록 설정되어 있기 때문이다.
확대를 방지하기 위해서 사용할 수 있는 여러가지 방법이 있는데, 그 중 두가지를 소개해 보겠다.
meta 태그에 maximum-scale=1, user-scalable=no 추가하기
maximun-scale이나 minimun=scale을 1로 지정하면 사용자의 확대나 축소를 방지할 수 있다.
하지만 최신 iOS기기에서는 접근성을 위해 maximumScale이 동작하지 않도록 되어있어 input의 focus 확대만 막는 효과를 볼 수 있고, 안드로이드 유저나 최신 iOS를 사용하지 않는다면 사용자의 모든 확대를 막아버릴 수 있어 주의해야한다.(w3c에서 정의한 접근성 규칙에 위반되기도 한다)
user-scalable 속성은 사용자가 스케일을 조절할 수 있는지의 여부를 지정하는 속성이다.
값으로는 yes / no를 입력하는데, 기본값은 yes지만 대부분 no를 지정한다.
no로 설정할 경우 사용자가 input 필드에 텍스트를 입력할 경우 웹 페이지가 스크롤 되는 것을 막아준다.
현제 국내 웹 서비스 업체들은 대부분 위의 속성들을 사용하고 있지만 최근에 만들어진 많은 웹 사이트들은 이를 지정하지 않는 추세이다. 설정을 복잡하게 할 수록 새로운 디바이스와의 호환성을 고려하지 않을 수 없기 때문이다.
// html <meta> 태그
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, maximum-scale=1, width=device-width">
// NextJS 13 기준// app/layout.tsx 파일 export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app' viewport: { width: 'device-width', initialScale: 1, maximumScale: 1 } };
16px 이상으로 폰트 사이즈 설정 후 scale로 크기 조정
사실 가장 간단한 방법은 폰트사이즈를 최소 16px 이상으로 설정하는 것이다.아무리 모바일 사이즈라고 하더라도 16px 이하부터는 적절한 사이즈라고 보기 어렵다.
가장 확실한 방법이지만, 만약 기존의 디자인이 모두 16px 이하의 font-size로 되어있다면 디자인을 수정해야 한다는 문제가 발생한다.
이럴 경우 transform의 scale을 통해 줄이는 방식이 하나 있다.
예를들어, 입력 12px 크기의 입력태그를 만들고 싶다면 16px로 설정한 후에 scale을 통해 75%만큼 줄이는 것이다.
이렇게 되면 입력 필드의 논리적 글꼴 크기는 16px 이지만 텍스트는 12px로 표시된다.
input[type="text"] { border-radius: 5px; font-size: 12px; line-height: 20px; padding: 5px; width: 100%; } // scale 75% input[type="text"] { /* enlarge by 16/12 = 133.33% */ border-radius: 6.666666667px; font-size: 16px; line-height: 26.666666667px; padding: 6.666666667px; width: 133.333333333%; /* scale down by 12/16 = 75% */ transform: scale(0.75); transform-origin: left top; /* remove extra white space */ margin-bottom: -10px; margin-right: -33.333333333%; }
아래와 같이 포커스 시에 16px을 적용시켜 사용하는 방법도 있지만,
업데이트 이후 확대시키는 타이밍과 폰트에 css가 적용되는 순서가 애매하기 때문에 추천하지 않는다.input[type="text"]:focus, input[type="password"]:focus, textarea:focus, select:focus { font-size: 16px; }
+) 활발한 토론장을 구경하고 싶다면... : https://stackoverflow.com/questions/2989263/disable-auto-zoom-in-input-text-tag-safari-on-iphone/7655319#7655319
아무튼 폰트 사이즈는 16px 이상으로 쓰는게 최고...!!
iOS 권장 최소 터치 영역
iOS Safari는 터치 입력을 고려하기 때문에 클릭 영역이 너무 작으면 작동이 어려울 수 있다.
iOS에서는 44px을 최소 터치 영역으로 권장하고 있다.
'html, 스타일시트(css, scss)' 카테고리의 다른 글
[html] 개발자도구에서 미디어쿼리가 안되는 이유 (2) | 2024.12.17 |
---|---|
[html / css ] 사용자 드래그(drag) 막는 방법 (0) | 2024.08.01 |
[html/css] Transition과 Animation의 차이 (수정중) (0) | 2024.07.04 |
[html/css] transform 변형 속성으로 육면체 만들기 (0) | 2024.07.04 |
[html/css] Vertical-align 속성으로 가로 정렬하기(수정중) (0) | 2024.06.28 |