Skip to content

Instantly share code, notes, and snippets.

@black7375
Created May 29, 2021 14:29
Show Gist options
  • Save black7375/381950352a76f0336f2abe9eb6b1fff1 to your computer and use it in GitHub Desktop.
Save black7375/381950352a76f0336f2abe9eb6b1fff1 to your computer and use it in GitHub Desktop.
Fluent Reveal Effect Typescript Version
// This is Refactoring Version of https://github.com/d2phap/fluent-reveal-effect
// Plan to PR when there is time
// ** Interfaces ***************************************************************
// Global
interface resourceI {
oriBg: string;
el: HTMLElement;
}
// Mouse
type is_pressedI = [boolean]; // For reference variable
interface AreaI {
left: number;
right: number;
top: number;
bottom: number;
}
// Effect
interface effectOptionsI {
lightColor: string;
gradientSize: number;
clickEffect: boolean;
isContainer: boolean;
children: {
borderSelector: string,
elementSelector: string,
lightColor: string,
gradientSize: number
};
}
type userEffectOptionsI = Partial<effectOptionsI>;
interface enableEffectFuncI {
(element: resourceI,
options: effectOptionsI,
is_pressed: is_pressedI ): void
}
// ** Postion ******************************************************************
function getOffset (element: HTMLElement) {
const bounding = element.getBoundingClientRect();
return ({
top: bounding.top,
left: bounding.left
});
}
// with Mouse
function getXY(element: HTMLElement, e: MouseEvent) {
const offset = getOffset(element);
const x = e.pageX - offset.left - window.scrollX;
const y = e.pageY - offset.top - window.scrollY;
return [x, y];
}
// for Container
function intersectRect(r1: AreaI, r2: AreaI) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
function isIntersected(element: HTMLElement, cursor_x: number, cursor_y: number, gradientSize: number) {
const cursor_area: AreaI = {
left: cursor_x - gradientSize,
right: cursor_x + gradientSize,
top: cursor_y - gradientSize,
bottom: cursor_y + gradientSize
};
const bounding = element.getBoundingClientRect();
const el_area: AreaI = {
left: bounding.left,
right: bounding.right,
top: bounding.top,
bottom: bounding.bottom
};
const result = intersectRect(cursor_area, el_area);
return result;
}
// ** CSS Effect ***************************************************************
function lightHoverEffect(gradientSize: number, x: number, y: number, lightColor: string) {
return `radial-gradient(circle ${gradientSize}px at ${x}px ${y}px, ${lightColor}, rgba(255,255,255,0))`;
}
function lightClickEffect(gradientSize: number, x: number, y: number, lightColor: string) {
return `${lightHoverEffect(gradientSize, x, y, lightColor)}, radial-gradient(circle ${70}px at ${x}px ${y}px, rgba(255,255,255,0), ${lightColor}, rgba(255,255,255,0), rgba(255,255,255,0))`;
}
// ** Basic Draw Effect ********************************************************
function drawEffect(
element: HTMLElement,
x: number,
y: number,
lightColor: string,
gradientSize: number,
cssLightEffect: string | null = null ) {
const lightBg = cssLightEffect === null
? lightHoverEffect(gradientSize, x, y, lightColor)
: cssLightEffect;
element.style.backgroundImage = lightBg;
}
// with Mouse
function drawHoverEffect(element: HTMLElement, lightColor: string, gradientSize: number, e: MouseEvent) {
const [x, y] = getXY(element, e);
drawEffect(element, x, y, lightColor, gradientSize);
}
function drawClickEffect(element: HTMLElement, lightColor: string, gradientSize: number, e: MouseEvent) {
const [x, y] = getXY(element, e);
const cssLightEffect = lightClickEffect(gradientSize, x, y, lightColor);
drawEffect(element, x, y, lightColor, gradientSize, cssLightEffect);
}
// ** SideEffect Draw Effect ***************************************************
function clearEffect(resource: resourceI, is_pressed: is_pressedI) {
is_pressed[0] = false;
resource.el.style.backgroundImage = resource.oriBg;
}
function drawContainerHoverEffect(resource: resourceI, lightColor: string, gradientSize: number,
is_pressed: is_pressedI, e: MouseEvent) {
const element = resource.el;
if (isIntersected(element, e.clientX, e.clientY, gradientSize)) {
drawHoverEffect(element, lightColor, gradientSize, e);
}
else {
clearEffect(resource, is_pressed);
}
}
// Wrapper
function enableBackgroundEffects(resource: resourceI, lightColor: string, gradientSize: number,
clickEffect: boolean, is_pressed: is_pressedI) {
const element = resource.el;
element.addEventListener("mousemove", (e) => {
if (clickEffect && is_pressed[0]) {
drawClickEffect(element, lightColor, gradientSize, e);
}
else {
drawHoverEffect(element, lightColor, gradientSize, e);
}
});
element.addEventListener("mouseleave", (e) => {
clearEffect(resource, is_pressed);
});
}
function enableBorderEffects(resource: resourceI, childrenBorders: resourceI[], options: effectOptionsI, is_pressed: is_pressedI) {
const element = resource.el;
const childrenBorderL = childrenBorders.length;
element.addEventListener("mousemove", (e) => {
for (let i = 0; i < childrenBorderL; i++) {
drawContainerHoverEffect(childrenBorders[i], options.lightColor, options.gradientSize, is_pressed, e);
}
});
element.addEventListener("mouseleave", (e) => {
for (let i = 0; i < childrenBorderL; i++) {
clearEffect(childrenBorders[i], is_pressed);
}
});
}
function enableClickEffects(resource: resourceI, lightColor: string, gradientSize: number,
is_pressed: is_pressedI) {
const element = resource.el;
element.addEventListener("mousedown", (e) => {
is_pressed[0] = true;
drawClickEffect(element, lightColor, gradientSize, e);
});
element.addEventListener("mouseup", (e) => {
is_pressed[0] = false;
drawHoverEffect(element, lightColor, gradientSize, e);
});
}
// Interface
function enableNormalBackgroundEffetcs(resource: resourceI, options: effectOptionsI, is_pressed: is_pressedI) {
enableBackgroundEffects(resource, options.lightColor, options.gradientSize, options.clickEffect, is_pressed);
}
function enableChildrenBackgroundEffetcs(resource: resourceI, options: effectOptionsI, is_pressed: is_pressedI) {
enableBackgroundEffects(resource, options.children.lightColor, options.children.gradientSize, options.clickEffect, is_pressed);
}
function enableNormalClickEffects(resource: resourceI, options: effectOptionsI, is_pressed: is_pressedI) {
enableClickEffects(resource, options.lightColor, options.gradientSize, is_pressed);
}
function enableChildrenClickEffects(resource: resourceI, options: effectOptionsI, is_pressed: is_pressedI) {
enableClickEffects(resource, options.children.lightColor, options.children.gradientSize, is_pressed);
}
// ** Element Processing *******************************************************
function preProcessElement(element: HTMLElement): resourceI {
return ({
oriBg: getComputedStyle(element).backgroundImage,
el: element
});
}
function preProcessElements(elements: NodeListOf<HTMLElement>) {
const ressources: resourceI[] = [];
const elementsL = elements.length;
for(let i = 0; i < elementsL; i++) {
const element = elements[i];
ressources.push(preProcessElement(element));
}
return ressources;
}
function preProcessSelector(selector: string) {
return preProcessElements(document.querySelectorAll(selector));
}
// ** ApplyEffect **************************************************************
// Option
function applyEffectOption(userOptions: userEffectOptionsI): effectOptionsI {
const defaultOptions: effectOptionsI = {
lightColor: "rgba(255,255,255,0.25)",
gradientSize: 150,
clickEffect: false,
isContainer: false,
children: {
borderSelector: ".eff-reveal-border",
elementSelector: ".eff-reveal",
lightColor: "rgba(255,255,255,0.25)",
gradientSize: 150
}
};
return Object.assign(defaultOptions, userOptions);
}
// Children Effect
function applySingleChildrenEffect(
resource: resourceI, options: effectOptionsI, is_pressed: is_pressedI,
enableBackgroundEffectsFunc: enableEffectFuncI, enableClickEffectsFunc: enableEffectFuncI
) {
enableBackgroundEffectsFunc(resource, options, is_pressed);
if (options.clickEffect) {
enableClickEffectsFunc(resource, options, is_pressed);
}
}
function applyChildrenEffect(
resources: resourceI[], options: effectOptionsI, is_pressed: is_pressedI,
enableBackgroundEffectsFunc: enableEffectFuncI, enableClickEffectsFunc: enableEffectFuncI
) {
const resourceL = resources.length;
for(let i = 0; i < resourceL; i++) {
const resource = resources[i];
applySingleChildrenEffect(resource, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
}
// Container Effect
function applySingleContainerEffect(
resource: resourceI, options: effectOptionsI, is_pressed: is_pressedI,
enableBackgroundEffectsFunc: enableEffectFuncI, enableClickEffectsFunc: enableEffectFuncI
) {
// Container
const childrenBorders = preProcessSelector(options.children.borderSelector);
enableBorderEffects(resource, childrenBorders, options, is_pressed);
// Children
const childrens = preProcessSelector(options.children.elementSelector);
applyChildrenEffect(childrens, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
function applyContainerEffect(
resources: resourceI[], options: effectOptionsI, is_pressed: is_pressedI,
enableBackgroundEffectsFunc: enableEffectFuncI, enableClickEffectsFunc: enableEffectFuncI
) {
const resourceL = resources.length;
for(let i = 0; i < resourceL; i++) {
const resource = resources[i];
applySingleContainerEffect(resource, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
}
// Apply Effect
function applySingleEffect(element: HTMLElement, userOptions: userEffectOptionsI = {}) {
const is_pressed: is_pressedI = [false];
const options = applyEffectOption(userOptions);
const resource = preProcessElement(element);
if (!options.isContainer) {
const enableBackgroundEffectsFunc = enableNormalBackgroundEffetcs;
const enableClickEffectsFunc = enableNormalClickEffects;
applySingleChildrenEffect(resource, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
else {
const enableBackgroundEffectsFunc = enableChildrenBackgroundEffetcs;
const enableClickEffectsFunc = enableChildrenClickEffects;
applySingleContainerEffect(resource, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
}
function applyEffect(selector: string, userOptions: userEffectOptionsI = {}) {
const is_pressed: is_pressedI = [false];
const options = applyEffectOption(userOptions);
const resoures = preProcessSelector(selector);
if (!options.isContainer) {
const enableBackgroundEffectsFunc = enableNormalBackgroundEffetcs;
const enableClickEffectsFunc = enableNormalClickEffects;
applyChildrenEffect(resoures, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
else {
const enableBackgroundEffectsFunc = enableChildrenBackgroundEffetcs;
const enableClickEffectsFunc = enableChildrenClickEffects;
applyContainerEffect(resoures, options, is_pressed, enableBackgroundEffectsFunc, enableClickEffectsFunc);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment