はじめに
業務がバックエンド系のこともあり、
デザインとCSSの勉強も含めて
今回はNeumorphismなUIのポモロードタイマーを作ってみる
Neumorphismとは、(AIによる解説より)
ニューモフィズム (Neumorphism) は、UIデザインのトレンドの一つです。リアルな物体のような質感や立体感を表現するのが特徴で、ユーザーインターフェースに深みと柔らかい印象を与えます。
主な特徴
* ソフトな立体感: シャドウとハイライトを使い、まるでボタンやカードがUIの背景から押し出されたような、または埋め込まれたような表現をします。
* ミニマルなデザイン: 装飾を抑えたシンプルでクリーンな見た目になります。
* 落ち着いた色使い: 背景と近い色調を用いることで、控えめで洗練された雰囲気を演出します。
デザインのポイント
* 控えめなシャドウとハイライト: 強すぎるシャドウやハイライトは、デザインを古臭く見せてしまうので注意が必要です。
* 適切なコントラスト: 背景と要素の色のコントラストが低すぎると、要素が認識しづらくなります。アクセシビリティにも配慮して、適切なコントラストを保つことが重要です。
* 繊細なグラデーション: 自然な立体感を出すために、繊細なグラデーションが使われます。
今回作るポモドーロタイマー
こんな感じのアプリを作成する。
(主にボタンなどに適用しているが柔らかな印象でかっこいい🧑🎨)
主な機能は
- ポモドーロタイマー
- ダークモード
成果物デモサイト
参考になるサイト
- uiverse.io
- neumorphism.io
プロジェクトの作成
今回の構成は、React(SPA)とCssライブラリは使わずにVanillaのCssで作成する。
-
localstorageを使い、Theme切り替え機能を実装
-
25min+5minが3回と25min+25minの1回の計140分のポモドーロタイマーにする
-
シンプルなUIを意識してその他はStartStopボタンとResetボタンのみ配置
App.jsx
function App() {
const dark = 'dark';
const light = 'light';
const [theme, setTheme] = useState(() => {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
return savedTheme;
}
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return prefersDark ? dark : light;
});
useEffect(() => {
localStorage.setItem('theme', theme);
document.body.classList.remove(light, dark);
document.body.classList.add(theme);
}, [theme]);
const toggleTheme = (newTheme) => {
setTheme(newTheme);
};
const longtime = 25 * 60;
const shorttime = 5 * 60;
const [timeIndex, setTimeIndex] = useState(0);
const timecycle = [longtime, shorttime, longtime, shorttime, longtime, shorttime, longtime, longtime]; // (25+5)*3 + (25+25)
const [timeLeft, setTimeLeft] = useState(timecycle[timeIndex]);
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
const [isActive, setIsActive] = useState(false);
useEffect(() => {
let interval = null;
if (isActive && timeLeft > 0) {
interval = setInterval(() => {
setTimeLeft(timeLeft => timeLeft - 1);
}, 1000);
} else if (!isActive && timeLeft !== 0) {
clearInterval(interval);
}
else if (!isActive && timeLeft == 0) {
setTimeLeft(timecycle[timeIndex]);
clearInterval(interval);
}
return () => clearInterval(interval);
}, [isActive, timeLeft, timeIndex]);
const handleStart = () => {
setIsActive(true);
};
const handleStop = () => {
setIsActive(false);
if (minutes == 0 && seconds == 0) {
setTimeIndex(x => x + 1 > timecycle.length - 1 ? 0 : x + 1);
}
};
const handleReset = () => {
setTimeLeft(timecycle[timeIndex]);
setIsActive(false);
};
return (
<>
<header>
<button className='themebtn' onClick={() => { toggleTheme(theme == dark ? light : dark); }}>🌞/🌛</button>
</header>
<main>
<div className='timer'>{minutes == 0 && seconds == 0 ? `Finish.` : `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`}</div>
<div className="cycle_expain">
<div className='count'>
{timecycle.map((item, index) => (
<div
key={index}
className={timeIndex == index ? (timeIndex % 2 == 1 ? 'timeindex_break' : 'timeindex_task') : 'timecycle'}
style={{ width: `${item / 60}px` }}
></div>
))}
</div>
<br />
<span>{timeIndex % 2 == 1 ? `Break ${timecycle[timeIndex] / 60}min.` : `Task ${timecycle[timeIndex] / 60}min.`}</span>
<br />
<span>1 cycle is 140min.</span>
</div>
{isActive ?
(<button onClick={() => { handleStop(); }}>STOP</button>)
: (<button onClick={() => { handleStart(); }}>START</button>)}
<button onClick={() => { handleReset(); }}>RESET</button>
</main>
<footer>
<span> © 2024 <a href="https://github.com/shisojuice" target="_blank" rel="noopener noreferrer" _mstmutation="1">shisojuice</a> Pomodoro Timer. All rights reserved.</span>
</footer>
</>
)
}
上記の紹介したneumorphism.ioを参考にしつつ、
Neumorphismの柔らかいデザインになるように
shadowとlinear-gradientを意識してCSSを適用する
App.css
:root {
--text-color: #090909;
--background-color: #e8e8e8;
--text-shadow: 6px 6px 12px #c5c5c5, -6px -6px 12px #ffffff;
--timer-color: #3ed83e;
--timecycle-box-shadow: inset 3px 3px 0px #d1d1d1, inset -3px -3px 0px #ffffff;
--timeindex_task-background: linear-gradient(145deg, #f58989, #ce7373);
--timeindex_task-box-shadow: 2px 2px 7px #ce7373, -1px -1px 7px #ff9393;
--timeindex_break-background: linear-gradient(145deg, #80b5ec, #6c98c7);
--timeindex_break-box-shadow: 2px 2px 7px #6c98c7, -1px -1px 7px #84baf3;
--button-active-box-shadow: inset 4px 4px 12px #c5c5c5, inset -4px -4px 12px #ffffff;
--anchor-color: #3c4bf4;
color: var(--text-color);
background-color: var(--background-color);
text-shadow: var(--text-shadow);
}
.dark {
--text-color: #fff;
--background-color: #212121;
--text-shadow: 6px 6px 12px #000, -6px -6px 12px #2f2f2f;
--timer-color: #8adc8a;
--timecycle-box-shadow: inset 3px 3px 0px #1a1a1a, inset -3px -3px 0px #282828;
--timeindex_task-background: linear-gradient(145deg, #b91c00, #9c1700);
--timeindex_task-box-shadow: 2px 2px 7px #9c1700, -1px -1px 7px #be1d00;
--timeindex_break-background: linear-gradient(145deg, #0060b9, #00519c);
--timeindex_break-box-shadow: 2px 2px 7px #00519c, -1px -1px 7px #0063be;
--button-active-box-shadow: inset 4px 4px 12px #c5c5c5, inset -4px -4px 12px #ffffff;
--anchor-color: #83b8e2;
color: var(--text-color);
background-color: var(--background-color);
text-shadow: var(--text-shadow);
}
.timer {
color: var(--timer-color);
text-shadow: var(--text-shadow);
background: var(--background-color);
font-size: 96px;
}
.timecycle {
height: 20px;
margin: auto 2px;
border-radius: 3px;
background: var(--background-color);
box-shadow: var(--timecycle-box-shadow);
}
.timeindex_task {
height: 20px;
margin: auto 2px;
border-radius: 3px;
background: var(--timeindex_task-background);
box-shadow: var(--timeindex_task-box-shadow);
}
.timeindex_break {
height: 20px;
margin: auto 2px;
border-radius: 3px;
background: var(--timeindex_break-background);
box-shadow: var(--timeindex_break-box-shadow);
}
button {
width: 144px;
color: var(--text-color);
padding: 0.7em 1.7em;
font-size: 18px;
border-radius: 0.5em;
background: var(--background-color);
border: 1px solid var(--background-color);
transition: all 0.3s;
box-shadow: var(--text-shadow);
}
button:active {
color: #666;
box-shadow: var(--button-active-box-shadow);
}
a {
color: var(--anchor-color);
}
成果物ソース
まとめ
今回は、Neumorphismなポモロードタイマーを作ってみた。
Neumorphismは、柔らかい印象かつモダンな雰囲気があり、かっこいい😎と思ったが、
コントラストが低くなりがちで視認性、アクセシビリティが悪くなってしまったので、
注意する必要があると思った。🤔
関連記事