Skip to content

Commit

Permalink
feat: automatically adjust background color to meet or exceed AA cont…
Browse files Browse the repository at this point in the history
…rast (#63)

Co-authored-by: Liam DeBeasi <[email protected]>
  • Loading branch information
liamdebeasi and liamdebeasi authored Apr 12, 2024
1 parent dd70d21 commit abce934
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 29 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ The selected foreground and background colors are displayed in their respective

The color palette tool can be used to bring up the browser's native color picker for more advanced color selection.

### Modifying Colors

Contrast can help suggest more accessible colors using the "Auto Adjust" tool. Clicking the wand icon will adjust the background color if necessary to bring the color contrast ratio up to at least 4.5:1 (AA).

### Copying Colors

The clipboard icon can be used to copy the foreground or background color to your clipboard for usage outside of the app.
Expand Down
Binary file modified assets/contrast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 19 additions & 19 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,14 @@
import { ref, watch } from 'vue';
import { ColorContrastCalc } from 'color-contrast-calc';
import { computeContrast } from './utils';
import DropperButton from './components/DropperButton.vue';
import CopyButton from './components/CopyButton.vue';
import PaletteButton from './components/PaletteButton.vue';
import ColorInput from './components/ColorInput.vue';
import ModeButton from './components/ModeButton.vue';
const getContrast = () => {
const foregroundRGB = ColorContrastCalc.colorFrom(foreground.value);
const backgroundRGB = ColorContrastCalc.colorFrom(background.value);
const ratio = foregroundRGB.contrastRatioAgainst(backgroundRGB);
/**
* Ratio should have at most
* two decimal places. Note that we
* do not round here because a
* ratio of 4.995 should not be
* founded up to 4.5.
*/
return ratio.toString().match(/^-?\d+(?:\.\d{0,2})?/)[0];
}
import AdjustButton from './components/AdjustButton.vue';
const getDeveloperMode = () => {
const res = localStorage.getItem('developermode');
Expand All @@ -42,7 +29,7 @@ const developerMode = ref(getDeveloperMode());
const foregroundTextColor = ref(background.value);
const backgroundTextColor = ref(foreground.value);
const contrast = ref(getContrast())
const contrast = ref(computeContrast(foreground.value, background.value))
const metaThemeColor = document.querySelector('meta[name=theme-color]') as HTMLMetaElement;
const updateMetaThemeColor = () => {
Expand Down Expand Up @@ -83,7 +70,7 @@ const computeRating = () => {
*/
watch([foreground, background], () => {
updateMetaThemeColor();
const ratio = contrast.value = getContrast();
const ratio = contrast.value = computeContrast(foreground.value, background.value);
/**
* Typically the textColor refs are the
Expand Down Expand Up @@ -147,7 +134,10 @@ updateMetaThemeColor();
<h2>{{ contrast }}</h2>
</span>

<ModeButton v-model="developerMode" :hint="developerMode === false ? 'Enter developer mode' : 'Exit developer mode'" />
<div class="button-column">
<ModeButton v-model="developerMode" :hint="developerMode === false ? 'Enter developer mode' : 'Exit developer mode'" />
<AdjustButton v-model="background" :foreground="foreground" hint="Adjust background color to meet AA color contrast level" />
</div>

</div>
<div class="foreground color-row">
Expand Down Expand Up @@ -307,4 +297,14 @@ updateMetaThemeColor();
.bottom-bar .color-row {
width: 100%; /* hack */
}
.button-column {
display: flex;
flex-direction: column;
position: absolute;
inset-inline-end: 0;
top: 8px;
}
</style>
56 changes: 56 additions & 0 deletions src/components/AdjustButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { colorWand, checkmark } from 'ionicons/icons';
import { computeContrast } from '../utils';
import { ColorContrastCalc } from 'color-contrast-calc';
import { ref } from 'vue';
const props = defineProps(['modelValue', 'foreground', 'hint']);
const emit = defineEmits(['update:modelValue']);
const showAdjustButton = ref(true);
const adjustColor = () => {
const contrast = computeContrast(props.foreground, props.modelValue);
if (contrast < 4.5) {
const foregroundColor = ColorContrastCalc.colorFrom(props.foreground);
const backgroundColor = ColorContrastCalc.colorFrom(props.modelValue);
const adjustedBackgroundColor = foregroundColor.findBrightnessThreshold(backgroundColor, 'AA');
emit('update:modelValue', adjustedBackgroundColor.hexCode);
}
showAdjustButton.value = false;
// Show a visual indicator that the operation succeeded to the clipboard.
setTimeout(() => {
showAdjustButton.value = true;
}, 3000);
}
</script>

<template>
<div class="button-wrapper">
<button v-if="showAdjustButton" @click="adjustColor()" class="app-icon" :title="props.hint" :aria-label="props.hint">
<ion-icon aria-hidden="true" :icon="colorWand"></ion-icon>
</button>
<div class="app-icon" v-if="!showAdjustButton">
<ion-icon aria-label="Background color adjusted" :icon="checkmark" class="checkmark"></ion-icon>
</div>
<div class="status" role="status" aria-live="assertive">
<p v-if="!showAdjustButton">Background color adjusted</p>
</div>
</div>
</template>

<style scoped>
.checkmark {
cursor: default;
}
.status {
opacity: 0;
position: absolute;
pointer-events: none;
}
</style>
11 changes: 1 addition & 10 deletions src/components/ModeButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,4 @@
<ion-icon aria-hidden="true" :icon="props.modelValue === true ? hammer : hammerOutline"></ion-icon>
</button>
</div>
</template>

<style scoped>
.button-wrapper {
position: absolute;
inset-inline-end: 0;
top: 8px;
}
</style>
</template>
14 changes: 14 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ColorContrastCalc } from 'color-contrast-calc';

export const computeContrast = (foreground: string, background: string) => {
const foregroundRGB = ColorContrastCalc.colorFrom(foreground);
const backgroundRGB = ColorContrastCalc.colorFrom(background);

const ratio = foregroundRGB.contrastRatioAgainst(backgroundRGB);

/**
* Ratio should have at most two decimal places. Note that we do not round here
* because a ratio of 4.995 should not be rounded up to 4.5.
*/
return ratio.toString().match(/^-?\d+(?:\.\d{0,2})?/)[0];
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/test.spec.ts-snapshots/contrast-default-firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit abce934

Please sign in to comment.