main.js ← App.js ← api/api.js
← components(floder) ← SearchInput.js
← SearchError.js
← SearchKeyword.js
← SearchResult.js
← ImageInfo.js
← ImageBanner.js
← Loading.js
← DarkMode.js
← lib ← LocalStorage.js
← LazyLoading.js
style.css 내부 주석이 달린 코드가 있습니다. prefers-color-scheme는 운영체제에 따라 dark/white 중 어떤 모드를 사용하는지 탐지 할 수 있습니다.
style.css
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
color: white;
}
}
widnow.matchMedia()는 주어진 미디어 쿼리 문자열의 분석 결과를 나타내는 객체를 반환합니다. 이를 활용해서 prefers-color-scheme를 분석하여 사용하는 모드를 탐지할 수 있습니다.
그에 따라 html body에 dark/white 모드 속성을 추가합니다.
DarkMode.js
export const DarkMode = () => {
var $theme = document.body.dataset.theme;
if (!$theme) {
$theme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
document.body.setAttribute("data-theme", $theme);
}
}
다크모드에 대한 이해가 완료되었으니, 이후 파일을 수정합니다. 다크모드를 사용자가 선택하여 적용할 수 있도록 토글을 생성하여 App.js에 렌더링합니다. 토글에 대한 스타일과 data-theme에 대한 스타일은 다음을 확인해서 사용해주세요.
DarkMode.js
export default function DarkMode({ $app }) {
var $theme = document.body.dataset.theme;
if (!$theme) {
$theme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
document.body.setAttribute("data-theme", $theme);
}
this.$target = document.createElement("label");
this.$target.className = "DarkModeToggle";
this.$checkbox = document.createElement("input");
this.$checkbox.type = "checkbox";
this.$toggle = document.createElement("span");
this.$toggle.className = "slider round";
this.$target.appendChild(this.$checkbox);
this.$target.appendChild(this.$toggle);
$app.appendChild(this.$target);
this.$target.addEventListener("click", (e) => {
const $themeMode = e.target.checked ? "dark" : "light";
document.body.setAttribute("data-theme", $themeMode);
});
}
style-toggle.css
.DarkModeToggle {
position: relative;
display: inline-block;
width: 44px;
height: 26px;
margin-bottom: 1em;
}
.DarkModeToggle input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked + .slider {
background-color: #2196f3;
}
input:checked + .slider:before {
transform: translateX(18px);
}
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
style.css
...
body[data-theme="dark"] {
background-color: #000;
color: white;
}
고양이 배너 섹션의 경우 이미지가 잘게 나누어저 전부 표현되거나, 이미지 중간 공백이 없거나, 이미지가 가운데로 표현되지 않는 경우가 많이 존재했습니다. 해당 불편함을 해소하기 위해 다음과 같이 스타일링했습니다.
.SlideShow {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
height: 300px;
margin: 1em auto;
}
.Slides {
display: flex;
flex-direction: row;
align-items: center;
overflow-x: hidden;
width: 100%;
height: 100%;
padding: 0;
margin: 0.125em;
}
.Slides > li {
display: flex;
list-style-type: none;
align-items: center;
width: 30%;
height: 90%;
padding: 0 0.55em;
}
.Slides > li > img {
width: 200px;
height: 210px;
object-fit: cover;
}
.prev,
.next {
cursor: pointer;
text-align: center;
font-weight: bold;
font-size: 32px;
width: 5%;
}
디바이스 길이에 따라 검색결과 개수를 row당 column개수를 적절히 변경해야합니다.
style-media.css
@media (max-width: 992px) {
.SearchResult {
grid-template-columns: repeat(3, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.SearchResult {
grid-template-columns: repeat(2, minmax(250px, 1fr));
}
}
@media (max-width: 576px) {
.SearchResult {
grid-template-columns: repeat(1, minmax(250px, 1fr));
}
}
해당 기능을 검색 결과에만 적용하지 않고, 검색 창, 슬라이드쇼 등 적용시켜 커스텀해보았습니다. 다음 전체적인 반응형 css를 참고하여 활용해주세요.
style-media.css
@media (max-width: 992px) {
.SearchRandom {
width: 9%;
font-size: 12px;
}
.SearchRandom > span {
font-size: 1.25em;
}
.SearchResult {
grid-template-columns: repeat(3, minmax(250px, 1fr));
}
.SearchError {
font-size: 56px;
}
.Slides > li {
padding: 0.495em;
}
.Slides > li > img {
width: 155px;
height: 165px;
}
}
@media (max-width: 768px) {
.SearchRandom {
width: 9%;
font-size: 8px;
}
.SearchRandom > span {
font-size: 1em;
}
.SearchResult {
grid-template-columns: repeat(2, minmax(250px, 1fr));
}
.SearchError {
font-size: 42px;
}
.ImageInfo .content-wrapper {
width: 100%;
}
.Slides > li {
padding: 0.3em;
}
.Slides > li > img {
width: 122.5px;
height: 130px;
}
}
@media (max-width: 576px) {
.SearchRandom {
width: 7%;
font-size: 6px;
}
.SearchRandom > span {
font-size: 0.75em;
}
.SearchResult {
grid-template-columns: repeat(1, minmax(250px, 1fr));
}
.SearchError {
font-size: 24px;
}
.Slides > li {
padding: 0.2em;
}
.Slides > li > img {
width: 90px;
height: 95px;
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
color: white;
}
}