I've written this Email
component which obfuscates the email until the user hovers over it:
import React, {
AnchorHTMLAttributes,
DetailedHTMLProps,
ReactNode,
useState,
} from "react";
import { percentEncodeParams } from "./utils";
function obfuscateEmail(email: string): JSX.Element {
const [username, domain] = email.split("@");
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
a>span.roe::after {
content: "@";
}
`,
}}
/>
{username}
<span className="roe" />
{domain}
</>
);
}
type Props = DetailedHTMLProps<
AnchorHTMLAttributes<HTMLAnchorElement>,
HTMLAnchorElement
> & {
/** blind carbon copy e-mail addresses */
bcc?: string[];
/** body of e-mail */
body?: string;
/** carbon copy e-mail addresses */
cc?: string[];
children?: ReactNode;
/** e-mail recipient address */
email: string;
/** subject of e-mail */
subject?: string;
};
export function Email({
bcc = [],
body = "",
cc = [],
children,
email,
subject = "",
...props
}: Props): JSX.Element {
const [hovered, setHovered] = useState(false);
const emailUrl = new URL(`mailto:${email}`);
// https://github.com/whatwg/url/issues/18#issuecomment-369865339
emailUrl.search = percentEncodeParams({ bcc, body, cc, subject });
function handleHover() {
setHovered(true);
}
const displayText = children || email;
const obfuscatedText = children || obfuscateEmail(email);
return (
<a
href={hovered ? emailUrl.href : "#"}
onFocus={handleHover}
onMouseOver={handleHover}
{...props}
>
{hovered ? displayText : obfuscatedText}
</a>
);
}
This is utils.js
:
type StringParam = [string, string];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isValidStringParam(param: any): param is StringParam {
const [, value] = param;
return typeof value === "string" && value.length > 0;
}
function paramToStringParam([key, value]: [
string,
string | string[]
]): StringParam {
return Array.isArray(value) ? [key, value.join(",")] : [key, value];
}
/*
Do not use URLSearchParams.
URLSearchParams turns spaces into '+':
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams#examples
Some email clients on mobile devices don't replace the '+' with spaces.
According to the mailto spec, spaces should be percent encoded:
https://datatracker.ietf.org/doc/html/rfc6068#ref-STD66
*/
export function percentEncodeParams(params: {
bcc?: string[];
body?: string;
cc?: string[];
subject?: string;
}): string {
return Object.entries(params)
.map(paramToStringParam)
.filter(isValidStringParam)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join("&");
}
The idea is to strip out the @
symbol and use CSS to render it until the user hovers over it, in which case it swaps it for the original email so the user can copy paste it or interact with it.
Any drawbacks to this approach or ideas to make it more robust and bullet proof?
Any a11y issues?
Any feedback in general is appreciated.
CodeSandbox working example: https://codesandbox.io/s/hopeful-hamilton-oolrz
dangerouslySetInnerHTML
and that "hover" and mobile devices are not the best friends. Another thing that MIGHT (probably not) happen is if a browser tries to transform your email automatically to a link it won't be able to as it's not recognized as such \$\endgroup\$