<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<link href="http://code.jquery.com/ui/1.13.1/themes/cupertino/jquery-ui.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="theme.css" />
<link rel="stylesheet" href="ripple.css" />
<header>
<span class="title">
CSSテーマサンプル
</span>
</header>
<main>
<div class="theme-selector">
<div class="theme-auto ripple">
<span>自動/環境に合わせる</span>
</div>
<div class="theme-light ripple">
<span>ライト</span>
</div>
<div class="theme-dark ripple">
<span>ダーク</span>
</div>
<div class="theme-color-pallet">
<label for="primary-color">プライマリ色</label>
<input id="primary-color" type="color" title="色"/>
</div>
</div>
</main>
.ripple {
position: relative;
display: block;
overflow: hidden;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
/* ripple-effect */
.ripple-effect {
position: absolute;
display: inline-block;
border-radius: 100%;
background: var(--ripple);
opacity: 0;
transform: scale(0);
animation: ripple-effect 600ms cubic-bezier(0, 0, 0, 1.0) forwards;
pointer-events: none;
}
@keyframes ripple-effect {
from {
opacity: 1;
transform: scale(0);
}
to {
opacity: .2;
transform: scale(1);
}
}
html,
body {
width: 100%;
height: 100%;
}
body {
display: grid;
grid-template-rows: var(--header-height) 1fr;
}
header {
position: relative;
z-index: 101;
display: grid;
background-color: var(--header-background);
box-shadow: var(--header-shadow);
color: var(--header-color);
text-align: center;
font-size: large;
grid-template-columns: auto 1fr auto;
}
header .title {
vertical-align: middle;
line-height: var(--header-height);
grid-column: 2;
}
main {
overflow-y: scroll;
height: 100%;
background-color: var(--main-background);
color: var(--main-color);
}
div.ripple {
margin: 1em;
background: var(--p-color);
color: var(--p-text);
padding: .8em .8em;
border-radius: 5px;
transition: all 300ms ease-out;
}
div.ripple:hover,
div.ripple:active {
background: var(--p-highlight);
}
div.ripple span{
vertical-align: middle;
}
div.theme-color-pallet {
margin: 1em;
}
:root {
--header-height: 48px;
--header-shadow: rgb(0 0 0 / 20%) 0px 2px 4px -1px,
rgb(0 0 0 / 14%) 0px 4px 5px 0px,
rgb(0 0 0 / 12%) 0px 1px 10px 0px;
--ripple: white;
}
:root,
:root[theme="light"] {
--header-background: #4682b4;
--header-color: #f5f5f5;
--main-background: #f5f5f5;
--main-color: #222222;
--p-color: #4682b4;
--p-highlight: #86afd0;
--p-text: #f5f5f5;
}
:root[theme="dark"] {
--header-background: #444444;
--header-color: #dddddd;
--main-background: #333333;
--main-color: #dddddd;
--p-color: #274863;
--p-highlight: #437dad;
--p-text: #f5f5f5;
}
/**
* マウスダウンのリップルイベントハンドラ
* @param {*} e
*/
function onRipple (e) {
const target = $(e.currentTarget)
const targetW = target.innerWidth()
const targetH = target.innerHeight()
const x = e.offsetX
const y = e.offsetY
const size = Number.parseInt(Math.max(x, y, targetW - x, targetH - y) * 2)
const w = size
const h = size
const effect = $('<span class="ripple-effect"></span>')
.css({
left: x - w / 2,
top: y - h / 2,
width: size,
height: size
})
.appendTo(this)
const onMouseUp = (e) => {
effect.remove()
target.off('mouseup', onMouseUp)
}
target.on('mouseup', onMouseUp)
}
/**
* リップル初期化
*/
function initRipples () {
$('.ripple')
.mousedown(onRipple)
}
const ThemeMode = {
Auto: null,
Dark: 'dark',
Light: 'light'
}
let themMode = ThemeMode.Auto
function getBrowserTheme (mediaQueryList) {
return mediaQueryList.matches ? 'dark' : 'light'
}
function setTheme (mediaQueryList) {
const theme = themMode ?? getBrowserTheme(mediaQueryList)
const htmlElement = document.body.parentElement
htmlElement.setAttribute('theme', theme)
const style = getComputedStyle(htmlElement)
const rgb = style.getPropertyValue('--p-color').trim()
$('#primary-color').val(rgb)
clearCustomColorPallet()
}
function clearCustomColorPallet() {
const bodyStyle = document.body.style
bodyStyle.removeProperty('--p-color')
bodyStyle.removeProperty('--p-highlight')
}
const mql = window.matchMedia('(prefers-color-scheme: dark)')
mql.addEventListener('change', setTheme)
setTheme(mql)
$(() => {
initRipples()
$('.theme-auto').on('click', () => {
themMode = ThemeMode.Auto
setTheme(mql)
})
$('.theme-light').on('click', () => {
themMode = ThemeMode.Light
setTheme(mql)
})
$('.theme-dark').on('click', () => {
themMode = ThemeMode.Dark
setTheme(mql)
})
$('#primary-color').on('change', () => {
const body = document.body
const computedStyle = getComputedStyle(body)
const themeRgb = computedStyle.getPropertyValue('--p-color').trim()
const pickerRgb = $('#primary-color').val()
console.log(pickerRgb)
if (themeRgb !== pickerRgb) {
const r = Number.parseInt(pickerRgb.substring(1, 3), 16)
const g = Number.parseInt(pickerRgb.substring(3, 5), 16)
const b = Number.parseInt(pickerRgb.substring(5, 7), 16)
const { h, s, l } = rgba2hsla({r, g, b})
body.style.setProperty('--p-color', pickerRgb)
body.style.setProperty('--p-highlight', `hsl(${h}, ${s}%, ${Math.min(l + 20, 100)}%)`)
} else {
clearCustomColorPallet()
}
})
})
const rgba2hsla = ({ r, g, b, a }) => {
console.log(r, g, b)
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const hsla = {
h: 0,
s: 0,
l: (max + min) / 2,
a: a ?? 255
}
const diff = max - min
if (min !== max) {
if (max === r) {
hsla.h = 60 * (g - b) / diff
} else if (max === g) {
hsla.h = 60 * (b - r) / diff + 120
} else if (max === b) {
hsla.h = 60 * (r - g) / diff + 240
}
}
hsla.s = diff / ((hsla.l <= 127) ? (max + min) : 510 - diff)
if (hsla.h < 0) {
hsla.h += 360
}
hsla.h = Math.round(hsla.h)
hsla.s = Math.round(hsla.s * 100)
hsla.l = Math.round((hsla.l * 100 / 255))
hsla.a = hsla.a / 255
return hsla
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.