Skip to content

Commit

Permalink
- added the 'preserve3dEnable' prop for the ability to enable/disable…
Browse files Browse the repository at this point in the history
… 'transform-style: preserve-3d' when needed

- added test for 'preserve3dEnable' prop and updated readme

- added support for passing TiltRef, FlipTiltRef, and ParallaxRef as controlElement

- added requestAnimationFrame() to some transform set calls that were missing it

- bugfix: fixed a bug where on re-render glares wouldn't be applied

- bigfix: ref.tilt() now applies the box shadow in addition to scale

- bugfix: added 'verticl-align: middle' to the container style to fix the empty white space that appeared below it (because of being inline-block)

- bugfix: now when disabled, the tilt will be reset on re-render
  • Loading branch information
rashidshamloo committed Aug 31, 2023
1 parent d6819b3 commit bf5fb41
Show file tree
Hide file tree
Showing 8 changed files with 19,053 additions and 48 deletions.
4 changes: 2 additions & 2 deletions README.md

Large diffs are not rendered by default.

Binary file modified cypress/videos/index.cy.tsx.mp4
Binary file not shown.
18,938 changes: 18,938 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "react-next-tilt",
"private": false,
"version": "0.0.8",
"version": "0.0.9",
"description": "A Performant Customizable Tilt Component for React",
"main": "./dist/react-next-tilt.umd.cjs",
"module": "./dist/react-next-tilt.js",
Expand Down Expand Up @@ -74,26 +74,26 @@
"@storybook/react-vite": "^7.1.1",
"@storybook/testing-library": "^0.0.14-next.2",
"@storybook/theming": "^7.1.1",
"@types/node": "^20.3.1",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitejs/plugin-react": "^4.0.0",
"cypress": "^12.17.0",
"eslint": "^8.38.0",
"@types/node": "^20.5.7",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.0.4",
"cypress": "^12.17.4",
"eslint": "^8.48.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"eslint-plugin-storybook": "^0.6.12",
"eslint-plugin-react-refresh": "^0.3.5",
"eslint-plugin-storybook": "^0.6.13",
"gh-pages": "^5.0.0",
"path": "^0.12.7",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^7.1.1",
"typescript": "^5.0.2",
"vite": "^4.3.9",
"vite-plugin-dts": "^3.2.0"
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-dts": "^3.5.3"
},
"files": [
"./dist/react-next-tilt.js",
Expand Down
21 changes: 21 additions & 0 deletions src/lib/__test__/index.cy.tsx
Expand Up @@ -104,6 +104,27 @@ describe('<Tilt />', () => {
.should('contain', 'perspective: 100px');
});

describe('Testing preserve3dEnable', () => {
it('Testing preserve3dEnable = true, style should contain "transform-style: preserve-3d"', () => {
cy.mount(<MockTilt preserve3dEnable={true} />);
cy.get('[data-testid="container"]')
.should('have.attr', 'style')
.should('contain', 'transform-style: preserve-3d');
cy.get('[data-testid="tilt"]')
.should('have.attr', 'style')
.should('contain', 'transform-style: preserve-3d');
});
it('Testing preserve3dEnable = false, style should not contain "transform-style: preserve-3d"', () => {
cy.mount(<MockTilt preserve3dEnable={false} />);
cy.get('[data-testid="container"]')
.should('have.attr', 'style')
.should('not.contain', 'transform-style: preserve-3d');
cy.get('[data-testid="tilt"]')
.should('have.attr', 'style')
.should('not.contain', 'transform-style: preserve-3d');
});
});

describe('Testing scale', () => {
it('Testing initial scale (1.1), should contain style3d(1,1,1)', () => {
cy.mount(<MockTilt scale={1.1} />);
Expand Down
74 changes: 49 additions & 25 deletions src/lib/index.tsx
Expand Up @@ -70,6 +70,7 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(
fullPageListening = false,
controlElement,
controlElementOnly = false,
preserve3dEnable = true,
testIdEnable = false,
onTilt,
onReset,
Expand Down Expand Up @@ -141,6 +142,31 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(

// functions

// updates the "will-change" css property
const updateWillChange = useCallback((add = true) => {
requestAnimationFrame(() => {
if (tiltRef.current)
tiltRef.current.style.willChange = add ? 'transform' : '';
if (spotGlareRef.current)
spotGlareRef.current.style.willChange = add
? 'transform, opacity'
: '';
if (lineGlareRef.current)
lineGlareRef.current.style.willChange = add ? 'transform' : '';
});
}, []);

// updates the box-shadow css property on the tilt element
const updateBoxShadow = useCallback(
(add = true) => {
requestAnimationFrame(() => {
if (tiltRef.current && shadowEnable)
tiltRef.current.style.boxShadow = add ? shadow : '';
});
},
[shadow, shadowEnable]
);

// updates spot glare element's transform and opacity
const updateSpotGlare = useCallback((): void => {
if (!containerRef.current || !spotGlareRef.current || !offset.current)
Expand Down Expand Up @@ -240,13 +266,20 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(
// sets currentPosition based on the provided angle,
// sets tilt angle to it and updates glare elements
const tilt = useCallback(
(angle: Angle, changeScale = false) => {
(angle: Angle, changeScaleAndShadow = false) => {
setOffsetFromAngle(angle);
setTiltAngle(angle, changeScale);
setTiltAngle(angle, changeScaleAndShadow);
updateBoxShadow(changeScaleAndShadow);
updateLineGlare();
updateSpotGlare();
},
[updateLineGlare, setOffsetFromAngle, updateSpotGlare, setTiltAngle]
[
setOffsetFromAngle,
setTiltAngle,
updateBoxShadow,
updateLineGlare,
updateSpotGlare,
]
);

// resets tilt angle, line glare transform,
Expand Down Expand Up @@ -322,25 +355,6 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(
[fullPageListening]
);

// updates the "will-change" css property
const updateWillChange = useCallback((add = true) => {
if (tiltRef.current)
tiltRef.current.style.willChange = add ? 'transform' : '';
if (spotGlareRef.current)
spotGlareRef.current.style.willChange = add ? 'transform, opacity' : '';
if (lineGlareRef.current)
lineGlareRef.current.style.willChange = add ? 'transform' : '';
}, []);

// updates the box-shadow css property on the tilt element
const updateBoxShadow = useCallback(
(add = true) => {
if (tiltRef.current && shadowEnable)
tiltRef.current.style.boxShadow = add ? shadow : '';
},
[shadow, shadowEnable]
);

// TiltRef
useImperativeHandle(
ref,
Expand Down Expand Up @@ -466,7 +480,7 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(

// if controlElement is not an array, convert it to one
let controlElementArray: Array<
HTMLElement | RefObject<HTMLElement> | Document
HTMLElement | RefObject<unknown> | Document
>;
if (fullPageListening || !controlElement)
controlElementArray = [document];
Expand Down Expand Up @@ -561,6 +575,15 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(
ref={(el) => {
if (el) {
containerRef.current = el;

// if this is a re-render
if (offset.current) {
// if disabled, reset
if (disabled) reset();
// else tilt according to the offset
else tilt(getAngleFromOffset());
}

// if initial angle is set and this is not a re-render,
// set initial angle by calling reset
if ((initialAngleX || initialAngleY) && !offset.current) reset();
Expand All @@ -570,11 +593,12 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(
style={Object.assign(
{
display: 'inline-block',
verticalAlign: 'middle',
width,
height,
borderRadius,
perspective,
transformStyle: 'preserve-3d',
transformStyle: preserve3dEnable ? 'preserve-3d' : undefined,
backfaceVisibility: 'hidden',
filter: disabled ? disabledFilter : undefined,
},
Expand Down Expand Up @@ -653,7 +677,7 @@ const NextTilt = forwardRef<TiltRef, TiltProps>(
width: '100%',
height: '100%',
borderRadius,
transformStyle: 'preserve-3d',
transformStyle: preserve3dEnable ? 'preserve-3d' : undefined,
backfaceVisibility: 'hidden',
transition: CSSTransition,
transform: `rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`,
Expand Down
20 changes: 16 additions & 4 deletions src/lib/types/types.ts
Expand Up @@ -40,9 +40,9 @@ export interface TiltRef {
* Tilts the component to the given angle
*
* @param {Angle} angle - Tilt angle ({angleX: number, angleY: number})
* @param {boolean} [changeScale=false] - Whether to apply the scale property or not
* @param {boolean} [changeScaleAndShadow=false] - Whether to apply the scale and shadow properties or not
*/
tilt: (angle: Angle, changeScale?: boolean) => void;
tilt: (angle: Angle, changeScaleAndShadow?: boolean) => void;
/**
* Resets the component (rotation/scale and glare effects)
*/
Expand Down Expand Up @@ -474,8 +474,8 @@ export interface TiltProps extends HTMLAttributes<HTMLDivElement> {
*/
controlElement?:
| HTMLElement
| RefObject<HTMLElement>
| Array<HTMLElement | RefObject<HTMLElement>>;
| RefObject<unknown>
| Array<HTMLElement | RefObject<unknown>>;
/**
* If set to `true`, events will be disabled for the component and it will be controlled by the controlElement(s) only
*
Expand All @@ -486,6 +486,18 @@ export interface TiltProps extends HTMLAttributes<HTMLDivElement> {
* @see {@link https://rashidshamloo.github.io/react-next-tilt_demo/control-element Control Element Demo}
*/
controlElementOnly?: boolean;
/**
* If set to true, adds `transform-style: preserve-3d;` to the container and tilt elements
*
* @note Enable if you want to set up a parallax effect and translate elements along the `Z` axis
*
* Disable if you are having problems with blur
*
* @warning Can cause blur on scale (prevents re-rastering at higher scales by Chrome's compositor and the element is always rasterized at scale 1)
*
* @default true
*/
preserve3dEnable?: boolean;
/**
* Adds the `data-testid=...` property to all elements for testing purposes
*
Expand Down
16 changes: 13 additions & 3 deletions src/lib/utility/utility.ts
Expand Up @@ -183,16 +183,26 @@ export const getLineGlareTransform = (

// gets HTMLElement from the union
export const getHTMLElement = (
el: HTMLElement | RefObject<HTMLElement> | Document
el: HTMLElement | RefObject<unknown> | Document
): HTMLElement | undefined => {
// if it's an HTMLElement, return it
if (el instanceof HTMLElement) return el;

// if it's the document, case it to HTMLElement and return it
if (el instanceof Document) return document as unknown as HTMLElement;

// if it's a "RefObject" and "ref.current" is not null, return it
if (el.current) return el.current;
// if it's a "RefObject" and "ref.current.element" is an HTMLElement, return it
// (for TiltRef, FlipTiltRef, and ParallaxRef)
if (
el.current &&
el.current instanceof Object &&
'element' in el.current &&
el.current.element instanceof HTMLElement
)
return el.current.element;

// if it's a "RefObject" and "ref.current" is an HTMLElement, return it
if (el.current && el.current instanceof HTMLElement) return el.current;

// otherwise, return undefined
return undefined;
Expand Down

0 comments on commit bf5fb41

Please sign in to comment.