mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-26 10:34:09 +03:00
feat: add 3d card
This commit is contained in:
parent
71c2bb0813
commit
52cc5d813f
@ -172,6 +172,7 @@ export default class SystemPage extends React.Component {
|
||||
<SidebarLink url={url} href="/_/system/avatar-group" title="Avatar Group" />
|
||||
<SidebarLink url={url} href="/_/system/buttons" title="Buttons" />
|
||||
<SidebarLink url={url} href="/_/system/card-tabs" title="Card Tabs" />
|
||||
<SidebarLink url={url} href="/_/system/card-3d" title="3D Card" />
|
||||
<SidebarLink url={url} href="/_/system/checkboxes" title="Checkboxes" />
|
||||
<SidebarLink url={url} href="/_/system/colors" title="Colors" />
|
||||
<SidebarLink url={url} href="/_/system/dropdowns" title="Dropdowns" />
|
||||
|
258
components/system/components/Card3D.js
Normal file
258
components/system/components/Card3D.js
Normal file
@ -0,0 +1,258 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_WRAPPER = css`
|
||||
display: inline-block;
|
||||
/* height: 100%; */
|
||||
/* width: 100%; */
|
||||
height: 200px;
|
||||
width: 320px;
|
||||
border-radius: 8px;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-tap-highlight-color: rgba(#000, 0);
|
||||
|
||||
.container.over .shadow {
|
||||
box-shadow: 0 45px 100px rgba(14, 21, 47, 0.4), 0 16px 40px rgba(14, 21, 47, 0.4);
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_CONTAINER = css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease-out;
|
||||
`;
|
||||
|
||||
const STYLES_SHADOW = css`
|
||||
position: absolute;
|
||||
top: 5%;
|
||||
left: 5%;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
transition: all 0.2s ease-out;
|
||||
box-shadow: 0 8px 30px rgba(14, 21, 47, 0.6);
|
||||
`;
|
||||
|
||||
const STYLES_LAYER = css`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transform-style: preserve-3d;
|
||||
`;
|
||||
|
||||
const STYLES_SHINE = css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0) 60%);
|
||||
`;
|
||||
|
||||
const STYLES_RENDERED_LAYER = css`
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
background-size: cover;
|
||||
transition: all 0.1s ease-out;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
`;
|
||||
|
||||
const Card3D = ({ children }) => {
|
||||
const wrapper = React.useRef();
|
||||
|
||||
const [width, setWidth] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
const width = getWidth(wrapper.current);
|
||||
setWidth(width);
|
||||
}, [width]);
|
||||
|
||||
const _handleMouseMove = (...args) => processMovement(...args);
|
||||
const _handleMouseEnter = (...args) => processEnter(...args);
|
||||
const _handleMouseLeave = (...args) => processExit(...args);
|
||||
|
||||
const _handleTouchMove = (e, ...rest) => {
|
||||
if (window.preventScroll) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
processMovement(e, ...rest);
|
||||
};
|
||||
|
||||
const _handleTouchStart = (...args) => {
|
||||
window.preventScroll = true;
|
||||
processEnter(...args);
|
||||
};
|
||||
|
||||
const _handleTouchEnd = (...args) => {
|
||||
window.preventScroll = false;
|
||||
processExit(...args);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
let layersNode = document.querySelectorAll(".rendered-layer");
|
||||
let layers = Array.from(layersNode);
|
||||
let shine = document.querySelectorAll(".shine")[0];
|
||||
|
||||
// desktop devices
|
||||
wrapper.current.addEventListener("mousemove", (e) =>
|
||||
_handleMouseMove(e, false, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
wrapper.current.addEventListener("mouseenter", (e) => _handleMouseEnter(e, wrapper.current));
|
||||
wrapper.current.addEventListener("mouseleave", (e) =>
|
||||
_handleMouseLeave(e, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
|
||||
// mobile devices
|
||||
wrapper.current.addEventListener("touchmove", (e) =>
|
||||
_handleTouchMove(e, false, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
wrapper.current.addEventListener("touchstart", (e) => _handleTouchStart(e, wrapper.current));
|
||||
wrapper.current.addEventListener("touchend", (e) =>
|
||||
_handleTouchEnd(e, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
|
||||
return () => {
|
||||
// desktop devices
|
||||
wrapper.current.removeEventListener("mousemove", (e) =>
|
||||
_handleMouseMove(e, false, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
wrapper.current.removeEventListener("mouseenter", (e) =>
|
||||
_handleMouseEnter(e, wrapper.current)
|
||||
);
|
||||
wrapper.current.removeEventListener("mouseleave", (e) =>
|
||||
_handleMouseLeave(e, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
|
||||
// mobile devices
|
||||
wrapper.current.removeEventListener("touchmove", (e) =>
|
||||
_handleTouchMove(e, false, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
wrapper.current.removeEventListener("touchstart", (e) =>
|
||||
_handleTouchStart(e, wrapper.current)
|
||||
);
|
||||
wrapper.current.removeEventListener("touchend", (e) =>
|
||||
_handleTouchEnd(e, wrapper.current, layers, layers.length, shine)
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div css={STYLES_WRAPPER} ref={wrapper} style={{ transform: `perspective(${width * 3}px)` }}>
|
||||
<div css={STYLES_CONTAINER} className="container">
|
||||
<div className="shadow" css={STYLES_SHADOW} />
|
||||
<div className="layer" css={STYLES_LAYER}>
|
||||
<div
|
||||
className="rendered-layer"
|
||||
css={STYLES_RENDERED_LAYER}
|
||||
style={{
|
||||
backgroundImage: "url('http://robindelaporte.fr/codepen/visa-bg.jpg')",
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="rendered-layer"
|
||||
css={STYLES_RENDERED_LAYER}
|
||||
style={{
|
||||
backgroundImage: "url('http://robindelaporte.fr/codepen/visa.png')",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="shine" css={STYLES_SHINE} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Card3D;
|
||||
|
||||
function getWidth(elem) {
|
||||
let width;
|
||||
if (elem) {
|
||||
width = elem.clientWidth || elem.offsetWidth || elem.scrollWidth;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
function processMovement(e, touchEnabled, elem, layers, totalLayers, shine) {
|
||||
let html = document.getElementsByTagName("html")[0];
|
||||
let bodyScrollTop = document.body.scrollTop || html.scrollTop,
|
||||
bodyScrollLeft = document.body.scrollLeft,
|
||||
pageX = touchEnabled ? e.touches[0].pageX : e.pageX,
|
||||
pageY = touchEnabled ? e.touches[0].pageY : e.pageY,
|
||||
offsets = elem.getBoundingClientRect(),
|
||||
w = elem.clientWidth || elem.offsetWidth || elem.scrollWidth,
|
||||
h = elem.clientHeight || elem.offsetHeight || elem.scrollHeight,
|
||||
wMultiple = 320 / w,
|
||||
offsetX = 0.52 - (pageX - offsets.left - bodyScrollLeft) / w,
|
||||
offsetY = 0.52 - (pageY - offsets.top - bodyScrollTop) / h,
|
||||
dy = pageY - offsets.top - bodyScrollTop - h / 2,
|
||||
dx = pageX - offsets.left - bodyScrollLeft - w / 2,
|
||||
yRotate = (offsetX - dx) * (0.07 * wMultiple),
|
||||
xRotate = (dy - offsetY) * (0.1 * wMultiple),
|
||||
imgCSS = "rotateX(" + xRotate + "deg) rotateY(" + yRotate + "deg)",
|
||||
arad = Math.atan2(dy, dx),
|
||||
angle = (arad * 180) / Math.PI - 90;
|
||||
|
||||
if (angle < 0) {
|
||||
angle = angle + 360;
|
||||
}
|
||||
|
||||
if (elem.firstChild.className.indexOf(" over") !== -1) {
|
||||
imgCSS += " scale3d(1.07,1.07,1.07)";
|
||||
}
|
||||
elem.firstChild.style.transform = imgCSS;
|
||||
|
||||
shine.style.background =
|
||||
"linear-gradient(" +
|
||||
angle +
|
||||
"deg, rgba(255,255,255," +
|
||||
((pageY - offsets.top - bodyScrollTop) / h) * 0.4 +
|
||||
") 0%,rgba(255,255,255,0) 80%)";
|
||||
shine.style.transform =
|
||||
"translateX(" +
|
||||
offsetX * totalLayers -
|
||||
0.1 +
|
||||
"px) translateY(" +
|
||||
offsetY * totalLayers -
|
||||
0.1 +
|
||||
"px)";
|
||||
|
||||
let revNum = totalLayers;
|
||||
for (let ly = 0; ly < totalLayers; ly++) {
|
||||
layers[ly].style.transform =
|
||||
"translateX(" +
|
||||
offsetX * revNum * ((ly * 2.5) / wMultiple) +
|
||||
"px) translateY(" +
|
||||
offsetY * totalLayers * ((ly * 2.5) / wMultiple) +
|
||||
"px)";
|
||||
revNum--;
|
||||
}
|
||||
}
|
||||
|
||||
function processEnter(e, elem) {
|
||||
elem.firstChild.className += " over";
|
||||
}
|
||||
|
||||
function processExit(e, elem, layers, totalLayers, shine) {
|
||||
let container = elem.firstChild;
|
||||
|
||||
container.className = container.className.replace(" over", "");
|
||||
container.style.transform = "";
|
||||
shine.style.cssText = "";
|
||||
|
||||
for (let ly = 0; ly < totalLayers; ly++) {
|
||||
layers[ly].style.transform = "";
|
||||
}
|
||||
}
|
@ -81,6 +81,8 @@ import { AvatarGroup } from "~/components/system/components/AvatarGroup";
|
||||
|
||||
import * as SVG from "~/common/svg";
|
||||
|
||||
import Card3D from "~/components/system/components/Card3D";
|
||||
|
||||
// NOTE(jim): Export everything.
|
||||
export {
|
||||
// NOTE(martina): Actions
|
||||
@ -113,6 +115,7 @@ export {
|
||||
ButtonDisabledFull,
|
||||
ButtonWarning,
|
||||
CardTabGroup,
|
||||
Card3D,
|
||||
CheckBox,
|
||||
CodeText,
|
||||
CodeTextarea,
|
||||
|
72
pages/_/system/card-3d.js
Normal file
72
pages/_/system/card-3d.js
Normal file
@ -0,0 +1,72 @@
|
||||
import * as React from "react";
|
||||
import * as System from "~/components/system";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
import SystemPage from "~/components/system/SystemPage";
|
||||
import ViewSourceLink from "~/components/system/ViewSourceLink";
|
||||
import CodeBlock from "~/components/system/CodeBlock";
|
||||
|
||||
export default class SystemPageCard3D extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SystemPage title="SDS: 3D Card" description="..." url="https://slate.host/_/system/card-3d">
|
||||
<System.H1>
|
||||
3D Card <ViewSourceLink file="system/card-3d.js" />
|
||||
</System.H1>
|
||||
<br />
|
||||
<br />
|
||||
<System.P>All of the colors the Filecoin Client uses.</System.P>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Imports</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<System.P>Import Constants.</System.P>
|
||||
<br />
|
||||
<br />
|
||||
<CodeBlock>{`import { Constants } from 'slate-react-system';`}</CodeBlock>
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Usage</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<System.P>Declare Constants.</System.P>
|
||||
<br />
|
||||
<CodeBlock>
|
||||
{`{Constants.system.white};
|
||||
|
||||
{Constants.system.foreground};
|
||||
|
||||
{Constants.system.gray};
|
||||
|
||||
{Constants.system.border};
|
||||
|
||||
{Constants.system.darkGray};
|
||||
|
||||
{Constants.system.black};
|
||||
|
||||
{Constants.system.pitchBlack};
|
||||
|
||||
{Constants.system.brand};
|
||||
|
||||
{Constants.system.link};
|
||||
|
||||
{Constants.system.green};
|
||||
|
||||
{Constants.system.yellow};
|
||||
|
||||
{Constants.system.red};`}
|
||||
</CodeBlock>
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Output</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<System.Card3D />
|
||||
</SystemPage>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user