Restez organisé à l'aide des collections
Enregistrez et classez les contenus selon vos préférences.
HTML
<label for="switch-1" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-1">
</label>
<label for="switch-2" class="gui-switch">
Indeterminate
<input type="checkbox" role="switch" id="switch-2">
<!-- TODO: Devsite - Removed inline handlers -->
<!-- <script>document.getElementById('switch-2').indeterminate = true</script> -->
</label>
<label for="switch-3" class="gui-switch">
Disabled
<input type="checkbox" role="switch" id="switch-3" disabled>
</label>
<label for="switch-4" class="gui-switch">
Disabled (checked)
<input type="checkbox" role="switch" id="switch-4" disabled checked>
</label>
<label for="switch-vertical" class="gui-switch -vertical">
Vertical
<input type="checkbox" role="switch" id="switch-vertical">
</label>
CSS
.gui-switch {
--thumb-size: 2rem;
--thumb: hsl(0 0% 100%);
--thumb-highlight: hsl(0 0% 0% / 25%);
--track-size: calc(var(--thumb-size) * 2);
--track-padding: 2px;
--track-inactive: hsl(80 0% 80%);
--track-active: hsl(80 60% 45%);
--thumb-color: var(--thumb);
--thumb-color-highlight: var(--thumb-highlight);
--track-color-inactive: var(--track-inactive);
--track-color-active: var(--track-active);
--isLTR: 1;
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
@media (prefers-color-scheme: dark) {
--thumb: hsl(0 0% 5%);
--thumb-highlight: hsl(0 0% 100% / 25%);
--track-inactive: hsl(80 0% 35%);
--track-active: hsl(80 60% 60%);
}
&:dir(rtl) {
--isLTR: -1;
}
&.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(calc(90deg * var(--isLTR) * -1));
touch-action: pan-x;
}
}
& > input {
--thumb-position: 0%;
--thumb-transition-duration: .25s;
padding: var(--track-padding);
background: var(--track-color-inactive);
inline-size: var(--track-size);
block-size: var(--thumb-size);
border-radius: var(--track-size);
appearance: none;
pointer-events: none;
touch-action: pan-y;
border: none;
outline-offset: 5px;
box-sizing: content-box;
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
transition: background-color .25s ease;
&::before {
--highlight-size: 0;
content: "";
cursor: pointer;
pointer-events: auto;
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
background: var(--thumb-color);
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
border-radius: 50%;
transform: translateX(var(--thumb-position));
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
&:not(:disabled):hover::before {
--highlight-size: .5rem;
}
&:checked {
background: var(--track-color-active);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
&:indeterminate {
--thumb-position: calc(
calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
* var(--isLTR)
);
}
&:disabled {
cursor: not-allowed;
--thumb-color: transparent;
&::before {
cursor: not-allowed;
box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);
@media (prefers-color-scheme: dark) {
box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
}
}
}
}
}
JS
const elements = document.querySelectorAll('.gui-switch')
const switches = new WeakMap()
const state = {
activethumb: null,
recentlyDragged: false,
}
const getStyle = (element, prop) =>
parseInt(
window.getComputedStyle(element)
.getPropertyValue(prop))
const getPseudoStyle = (element, prop) =>
parseInt(
window.getComputedStyle(element, ':before')
.getPropertyValue(prop))
const dragInit = event => {
if (event.target.disabled) return
state.activethumb = event.target
state.activethumb.addEventListener('pointermove', dragging)
state.activethumb.style.setProperty('--thumb-transition-duration', '0s')
}
const dragging = event => {
if (!state.activethumb) return
let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
let directionality = getStyle(state.activethumb, '--isLTR')
let track = (directionality === -1)
? (state.activethumb.clientWidth * -1) + thumbsize + padding
: 0
let pos = Math.round(event.offsetX - thumbsize / 2)
if (pos < bounds.lower) pos = 0
if (pos > bounds.upper) pos = bounds.upper
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}
const dragEnd = event => {
if (!state.activethumb) return
state.activethumb.checked = determineChecked()
if (state.activethumb.indeterminate)
state.activethumb.indeterminate = false
state.activethumb.style.removeProperty('--thumb-transition-duration')
state.activethumb.style.removeProperty('--thumb-position')
state.activethumb.removeEventListener('pointermove', dragging)
state.activethumb = null
padRelease()
}
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
const labelClick = event => {
if (
state.recentlyDragged ||
!event.target.classList.contains('gui-switch') ||
event.target.querySelector('input').disabled
) return
let checkbox = event.target.querySelector('input')
checkbox.checked = !checkbox.checked
event.preventDefault()
}
const determineChecked = () => {
let {bounds} = switches.get(state.activethumb.parentElement)
let curpos =
Math.abs(
parseInt(
state.activethumb.style.getPropertyValue('--thumb-position')))
if (!curpos) {
curpos = state.activethumb.checked
? bounds.lower
: bounds.upper
}
return curpos >= bounds.middle
}
elements.forEach(guiswitch => {
let checkbox = guiswitch.querySelector('input')
let thumbsize = getPseudoStyle(checkbox, 'width')
let padding = getStyle(checkbox, 'padding-left') + getStyle(checkbox, 'padding-right')
checkbox.addEventListener('pointerdown', dragInit)
checkbox.addEventListener('pointerup', dragEnd)
checkbox.addEventListener('click', preventBubbles)
guiswitch.addEventListener('click', labelClick)
switches.set(guiswitch, {
thumbsize,
padding,
bounds: {
lower: 0,
middle: (checkbox.clientWidth - padding) / 4,
upper: checkbox.clientWidth - thumbsize - padding,
},
})
})
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
Sauf indication contraire, le contenu de cette page est régi par une licence Creative Commons Attribution 4.0, et les échantillons de code sont régis par une licence Apache 2.0. Pour en savoir plus, consultez les Règles du site Google Developers. Java est une marque déposée d'Oracle et/ou de ses sociétés affiliées.
Dernière mise à jour le 2023/10/25 (UTC).
[[["Facile à comprendre","easyToUnderstand","thumb-up"],["J'ai pu résoudre mon problème","solvedMyProblem","thumb-up"],["Autre","otherUp","thumb-up"]],[["Il n'y a pas l'information dont j'ai besoin","missingTheInformationINeed","thumb-down"],["Trop compliqué/Trop d'étapes","tooComplicatedTooManySteps","thumb-down"],["Obsolète","outOfDate","thumb-down"],["Problème de traduction","translationIssue","thumb-down"],["Mauvais exemple/Erreur de code","samplesCodeIssue","thumb-down"],["Autre","otherDown","thumb-down"]],["Dernière mise à jour le 2023/10/25 (UTC)."],[],[]]