-
Notifications
You must be signed in to change notification settings - Fork 663
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #222 from hepengwei/feat_he
新增隐藏款量子纠缠效果
- Loading branch information
Showing
12 changed files
with
439 additions
and
14 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.