Skip to content

Commit

Permalink
Merge pull request #222 from hepengwei/feat_he
Browse files Browse the repository at this point in the history
新增隐藏款量子纠缠效果
  • Loading branch information
hepengwei authored Dec 12, 2023
2 parents d82d1c5 + 6bdcd53 commit 7b56b4e
Show file tree
Hide file tree
Showing 12 changed files with 439 additions and 14 deletions.
2 changes: 1 addition & 1 deletion docs/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/styles/main.css

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions src/components/InteractionEyes/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
.container {
$eyeSize: 28px;
$eyeMargin: 4px;
$heartSize: $eyeSize - 20px;
$eyeballSize: 10px;
$color: #ffffff;

box-sizing: border-box;
width: 2 * $eyeSize + $eyeMargin;
height: $eyeSize;
display: flex;
justify-content: space-between;

.eye {
box-sizing: border-box;
width: $eyeSize;
height: $eyeSize;
border-radius: 50%;
border: 1px solid $color;
display: flex;
align-items: center;
justify-content: center;

.heart {
width: $heartSize;
height: $heartSize;
transform: translateY(1px) rotate(-45deg) scale(1);
background-color: red;
position: relative;
transition: all;
animation: beat 0.5s infinite;

&::before {
content: "";
position: absolute;
width: $heartSize;
height: $heartSize;
border-radius: 50%;
top: calc($heartSize / -2);
left: 0;
background-color: red;
}

&::after {
content: "";
position: absolute;
width: $heartSize;
height: $heartSize;
border-radius: 50%;
top: 0;
right: calc($heartSize / -2);
background-color: red;
}
}

.eyeball {
box-sizing: border-box;
width: $eyeballSize;
height: $eyeballSize;
border-radius: 50%;
background-color: $color;
}
}

@keyframes beat {
0% {
transform: translateY(1px) rotate(-45deg) scale(1);
}
20% {
transform: translateY(1px) rotate(-45deg) scale(1.22);
}
50% {
transform: translateY(1px) rotate(-45deg) scale(0.84);
}
80% {
transform: translateY(1px) rotate(-45deg) scale(1);
}
100% {
transform: translateY(1px) rotate(-45deg) scale(1);
}
}
}
86 changes: 86 additions & 0 deletions src/components/InteractionEyes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* 量子纠缠交互眼
*/
import React, { useRef, useMemo, useEffect } from "react";
import { useGlobalContext } from "hooks/useGlobalContext";
import useQuantumEntanglement from "hooks/useQuantumEntanglement";
import { IFRAME_ID, THAT_PAGE_URL } from "constants/common";
import styles from "./index.module.scss";

const receiveKey = "bPageInfo";
const eyeSize = 28;
const eyeballSize = 10;

const InteractionEyes = () => {
const { locale } = useGlobalContext();
const containerRef = useRef<HTMLDivElement>(null);

const { thatPageInfo, resendMessage } = useQuantumEntanglement(
IFRAME_ID,
THAT_PAGE_URL,
receiveKey,
containerRef
);

// 切换中英文后交互眼位置变了,要通知另一页面
useEffect(() => {
resendMessage();
}, [locale]);

const eyebalInfo: { eyeballLeft: number; eyeballTop: number } | null =
useMemo(() => {
if (containerRef.current && thatPageInfo) {
const { x, y } = thatPageInfo;
const { top, left, width, height } =
containerRef.current.getBoundingClientRect();
const selfX = left + window.screenLeft + width / 2;
const selfY = top + window.screenTop + height / 2;
const dist = Math.hypot(x - selfX, y - selfY);
if (dist > eyeSize) {
const newEyeballLeft =
(((eyeSize - eyeballSize) / 2) * (x - selfX)) / dist;
const newEyeballTop =
(((eyeSize - eyeballSize) / 2) * (y - selfY)) / dist;
return { eyeballLeft: newEyeballLeft, eyeballTop: newEyeballTop };
}
return { eyeballLeft: 0, eyeballTop: 0 };
}
return null;
}, [thatPageInfo]);

return (
<div className={styles.container} ref={containerRef}>
{eyebalInfo && (
<>
<div className={styles.eye}>
{eyebalInfo.eyeballLeft === 0 && eyebalInfo.eyeballTop === 0 ? (
<div className={styles.heart} />
) : (
<div
className={styles.eyeball}
style={{
transform: `translate(${eyebalInfo.eyeballLeft}px, ${eyebalInfo.eyeballTop}px)`,
}}
/>
)}
</div>
<div className={styles.eye}>
{eyebalInfo.eyeballLeft === 0 && eyebalInfo.eyeballTop === 0 ? (
<div className={styles.heart} />
) : (
<div
className={styles.eyeball}
style={{
transform: `translate(${eyebalInfo.eyeballLeft}px, ${eyebalInfo.eyeballTop}px)`,
}}
/>
)}
</div>
</>
)}
<iframe id={IFRAME_ID} src={THAT_PAGE_URL} style={{ display: "none" }} />
</div>
);
};

export default InteractionEyes;
4 changes: 4 additions & 0 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ export const ECHART_COMMON_COLOR = [
"#3ba272",
"#9a60b4",
];

export const IFRAME_ID = "bIframe";
export const THAT_PAGE_URL = "https://michellez.cn";
export const THAT_PAGE_PASSWORD = '123456';
178 changes: 178 additions & 0 deletions src/hooks/useQuantumEntanglement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* 跨域页面实现量子纠缠实时通信
*/
import { useState, useRef, useEffect, useCallback, RefObject } from "react";
import useScreenPosition from "hooks/useScreenPosition";

interface ThatPageInfo {
pageId: string;
x: number;
y: number;
}

const useQuantumEntanglement = (
iframeId: string,
thatPageUrl: string,
receiveKey: string,
elementRef?: RefObject<HTMLDivElement | null>
) => {
const pageId = useRef<string>(Math.random().toString(36).substring(2));
const [thatPageInfo, setThatPageInfo] = useState<ThatPageInfo | null>(null);
const thatPageId = useRef<string>("");
const isThatPageReady = useRef<boolean>(false);
const sendTimer = useRef<number>(0);
const receiveTimer = useRef<number>(0);

const postInfo = useCallback(() => {
if (iframeId && isThatPageReady.current) {
const aIframe = document.getElementById(iframeId);
if (aIframe) {
let x = 0;
let y = 0;
if (elementRef?.current) {
const { top, left, width, height } =
elementRef.current.getBoundingClientRect();
x = left + window.screenLeft + width / 2;
y = top + window.screenTop + height / 2;
} else {
x = window.innerWidth / 2 + window.screenLeft;
y = window.innerHeight / 2 + window.screenTop;
}
const data = { pageId: pageId.current, x, y };
(aIframe as HTMLIFrameElement).contentWindow?.postMessage(
JSON.stringify(data),
"*"
);
}
}
}, []);

// 发送保活信息,维持通信状态
const postKeepAliveInfo = useCallback(() => {
if (iframeId && thatPageId.current && isThatPageReady.current) {
const aIframe = document.getElementById(iframeId);
if (aIframe) {
sendTimer.current = 0;
const data = {
pageId: pageId.current,
keepAlive: true,
timestamp: new Date().getTime(),
};
(aIframe as HTMLIFrameElement).contentWindow?.postMessage(
JSON.stringify(data),
"*"
);
if (!receiveTimer.current) {
receiveTimer.current = window.setTimeout(() => {
receiveTimer.current = 0;
window.localStorage.removeItem(receiveKey);
thatPageId.current = "";
setThatPageInfo(null);
}, 1500);
}
}
}
}, []);

const onMessage = useCallback((e: any) => {
if (e.origin !== thatPageUrl) return;
if (e.data) {
if (e.data.includes("keepAlive")) {
window.localStorage.setItem("keepAliveInfo", e.data);
} else {
window.localStorage.setItem(receiveKey, e.data);
}
} else {
window.localStorage.setItem("keepAliveInfo", "");
window.localStorage.setItem(receiveKey, "");
}
}, []);

const onStorage = useCallback((e: any) => {
if (e.key === "keepAliveInfo") {
if (e.newValue) {
const keepAliveInfo = JSON.parse(e.newValue);
if (keepAliveInfo && keepAliveInfo.pageId === thatPageId.current) {
// 如果收到了保活信息,则清除receiveTimer
if (receiveTimer.current) {
window.clearTimeout(receiveTimer.current);
receiveTimer.current = 0;
}
}
}
} else if (e.key === receiveKey) {
if (e.newValue) {
const thatPageInfo = JSON.parse(e.newValue);
if (thatPageInfo) {
thatPageId.current = thatPageInfo.pageId;
setThatPageInfo(thatPageInfo);
} else {
thatPageId.current = "";
setThatPageInfo(null);
}
} else {
thatPageId.current = "";
setThatPageInfo(null);
}
}
}, []);

// 获取localStorage中的另一页面的信息,并setThatPageInfo
const getLocalThatPageInfo = () => {
const thatPageInfoStr = window.localStorage.getItem(receiveKey);
if (thatPageInfoStr) {
const thatPageInfo = JSON.parse(thatPageInfoStr);
if (thatPageInfo) {
thatPageId.current = thatPageInfo.pageId;
setThatPageInfo(thatPageInfo);
} else {
thatPageId.current = "";
setThatPageInfo(null);
}
} else {
thatPageId.current = "";
setThatPageInfo(null);
}
};

const resendMessage = useCallback(() => {
postInfo();
getLocalThatPageInfo();
}, []);

useScreenPosition(resendMessage);

useEffect(() => {
window.addEventListener("message", onMessage, false);
if (iframeId) {
const aIframe = document.getElementById(iframeId);
if (aIframe) {
(aIframe as HTMLIFrameElement).onload = () => {
isThatPageReady.current = true;
resendMessage();
if (window.self === window.top) {
window.addEventListener("storage", onStorage);
window.addEventListener("resize", resendMessage);
sendTimer.current = window.setInterval(() => {
postKeepAliveInfo();
}, 600);
}
};
}
}

return () => {
window.removeEventListener("message", onMessage);
if (window.self === window.top) {
window.removeEventListener("storage", onStorage);
window.removeEventListener("resize", resendMessage);
sendTimer.current && window.clearInterval(sendTimer.current);
receiveTimer.current && window.clearTimeout(receiveTimer.current);
}
};
}, []);

return { thatPageInfo, resendMessage };
};

export default useQuantumEntanglement;
Loading

0 comments on commit 7b56b4e

Please sign in to comment.