diff --git a/lib/components/ScratchOffCard/index.tsx b/lib/components/ScratchOffCard/index.tsx index 7212bd6d..c8247c5a 100644 --- a/lib/components/ScratchOffCard/index.tsx +++ b/lib/components/ScratchOffCard/index.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState, memo } from 'react'; import styled from 'styled-components'; -import brushImg from './brush.jpg'; import { getAngleBetween, getDistanceBetween, @@ -9,6 +8,8 @@ import { } from './utils'; const DEFAULT_REVEAL_PERCENTAGE = 50; +const BRUSH_IMG = + 'data:image/jpeg;base64,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'; const StyledScratchOffCard = styled.div<{ width: number; height: number }>` position: relative; @@ -39,7 +40,7 @@ const StyledCoverImg = styled.img` visibility: hidden; `; -interface Props { +export interface ScratchOffCardProps { revealPercentage?: number; width: number; height: number; @@ -48,7 +49,7 @@ interface Props { handleReveal: () => void; } -const ScratchOffCard: React.FC = ({ +export const ScratchOffCard: React.FC = ({ revealPercentage = DEFAULT_REVEAL_PERCENTAGE, width, height, @@ -60,27 +61,30 @@ const ScratchOffCard: React.FC = ({ const canvasRef = useRef(null); const [isCoverImageReady, setIsCoverImageReady] = useState(false); - const handleCoverImgOnLoad = () => { - canvasRef.current - ?.getContext('2d') - .drawImage(coverImgRef.current, 0, 0, width, height); - setIsCoverImageReady(true); - }; + useEffect(() => { + coverImgRef.current!.src = coverImgSrc; + coverImgRef.current!.onload = () => { + setIsCoverImageReady(true); + }; + }, [coverImgSrc]); useEffect(() => { + if (!isCoverImageReady) { + return; + } + let isDrawing: boolean; let lastPoint: { x: number; y: number }; const canvas = canvasRef.current; - const ctx = canvasRef.current?.getContext('2d') as CanvasRenderingContext2D; - const image = coverImgRef.current as HTMLImageElement; + const ctx = canvas?.getContext('2d'); const brush = new Image(); - brush.src = brushImg; - ctx.drawImage(image, 0, 0, width, height); + brush.src = BRUSH_IMG; + ctx!.drawImage(coverImgRef.current!, 0, 0, width, height); const handleMouseDown = (e: MouseEvent | TouchEvent) => { isDrawing = true; - lastPoint = getMouse(e, canvasRef.current); + lastPoint = getMouse(e, canvas); }; const handleMouseMove = (e: MouseEvent | TouchEvent) => { @@ -90,7 +94,7 @@ const ScratchOffCard: React.FC = ({ e.preventDefault(); - const currentPoint = getMouse(e, canvasRef.current); + const currentPoint = getMouse(e, canvas); const dist = getDistanceBetween(lastPoint, currentPoint); const angle = getAngleBetween(lastPoint, currentPoint); let x; @@ -99,19 +103,16 @@ const ScratchOffCard: React.FC = ({ for (let i = 0; i < dist; i += 1) { x = lastPoint.x + Math.sin(angle) * i - 25; y = lastPoint.y + Math.cos(angle) * i - 25; - ctx.globalCompositeOperation = 'destination-out'; - ctx.drawImage(brush, x, y); + ctx!.globalCompositeOperation = 'destination-out'; + ctx!.drawImage(brush, x, y); } lastPoint = currentPoint; - const currentPercentage = getFilledInPixels(32, ctx, width, height); + const currentPercentage = getFilledInPixels(32, ctx!, width, height); - if ( - currentPercentage > revealPercentage && - canvasRef.current?.parentNode - ) { + if (currentPercentage > revealPercentage && canvas?.parentNode) { handleReveal(); - canvasRef.current?.parentNode.removeChild(canvasRef.current); + canvas?.parentNode.removeChild(canvas); } }; @@ -134,7 +135,7 @@ const ScratchOffCard: React.FC = ({ canvas?.removeEventListener('mouseup', handleMouseUp, false); canvas?.removeEventListener('touchend', handleMouseUp, false); }; - }, [handleReveal, revealPercentage, height, width]); + }, [handleReveal, revealPercentage, height, width, isCoverImageReady]); return ( @@ -142,15 +143,9 @@ const ScratchOffCard: React.FC = ({ {children} - + ); }; -export default memo(ScratchOffCard); +export default ScratchOffCard; diff --git a/lib/components/TransitionLeaderboardWrapper.tsx b/lib/components/TransitionLeaderboardWrapper.tsx index c8f17cf9..44db5312 100755 --- a/lib/components/TransitionLeaderboardWrapper.tsx +++ b/lib/components/TransitionLeaderboardWrapper.tsx @@ -1,12 +1,13 @@ -import React from 'react'; +import React, { memo } from 'react'; import styled from 'styled-components'; import useItemTransition, { ItemStyle } from '../hooks/useItemTransition'; import { User } from '../types'; -export interface Props { +export interface TransitionLeaderboardWrapperProps { user: User[]; rowCount: number; itemStyle: ItemStyle; + children: string | React.ReactNode; } const Wrapper = styled.div` @@ -19,7 +20,7 @@ const transitionStyle = { transition: 'all 0.5s ease 0.3s', }; -export const TransitionLeaderboardWrapper: React.FC = React.memo( +export const TransitionLeaderboardWrapper: React.FC = ({ user, itemStyle, rowCount, children }) => { const { itemTransitionStyle } = useItemTransition( itemStyle, @@ -45,7 +46,6 @@ export const TransitionLeaderboardWrapper: React.FC = React.memo( } return {renderChild()}; - }, -); + }; -export default TransitionLeaderboardWrapper; +export default memo(TransitionLeaderboardWrapper); diff --git a/lib/components/VirtualizedList.tsx b/lib/components/VirtualizedList.tsx index 89146fdc..a45fd78f 100644 --- a/lib/components/VirtualizedList.tsx +++ b/lib/components/VirtualizedList.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-children-prop */ -import React, { useEffect, useRef, useCallback, ReactNode } from 'react'; +import React, { useEffect, useRef, useCallback, ReactNode, memo } from 'react'; import { VariableSizeList } from 'react-window'; import styled from 'styled-components'; @@ -57,7 +57,7 @@ export interface IVirtualizedListProps { * 在 url 上加上 streamerUserID= 直播主的id,就會將畫面移動到直播主的位置 * @param IVirtualizedListProps 使用方法參考 IVirtualizedListProps 說明 */ -export const VirtualizedList: React.FC = ({ +const VirtualizedList: React.FC = ({ dataset, children, itemHeight = 80, @@ -142,4 +142,4 @@ export const VirtualizedList: React.FC = ({ ); }; -export default VirtualizedList; +export default memo(VirtualizedList); diff --git a/lib/components/index.ts b/lib/components/index.ts index 643367f1..e08ca94a 100755 --- a/lib/components/index.ts +++ b/lib/components/index.ts @@ -1,7 +1,13 @@ -import { TransitionLeaderboardWrapper } from './TransitionLeaderboardWrapper'; -import { VirtualizedList } from './VirtualizedList'; +import TransitionLeaderboardWrapper from './TransitionLeaderboardWrapper'; +import VirtualizedList from './VirtualizedList'; +import ScratchOffCard from './ScratchOffCard'; export * from './TransitionLeaderboardWrapper'; export * from './VirtualizedList'; +export * from './ScratchOffCard'; -export default { TransitionLeaderboardWrapper, VirtualizedList }; +export default { + TransitionLeaderboardWrapper, + VirtualizedList, + ScratchOffCard, +}; diff --git a/lib/template/offlineNormal.tsx b/lib/template/offlineNormal.tsx index fbaef26b..c74ab27b 100644 --- a/lib/template/offlineNormal.tsx +++ b/lib/template/offlineNormal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styled from 'styled-components'; import usePageData from '../hooks/usePageData'; -import { TransitionLeaderboardWrapper } from '../components/TransitionLeaderboardWrapper'; +import TransitionLeaderboardWrapper from '../components/TransitionLeaderboardWrapper'; import { ItemStyle } from '../hooks/useItemTransition'; import { qs } from '../utils'; diff --git a/playground/ScrollToStreamer.tsx b/playground/ScrollToStreamer.tsx index 69d3dbc3..6da4fb2d 100755 --- a/playground/ScrollToStreamer.tsx +++ b/playground/ScrollToStreamer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from 'styled-components'; -import { VirtualizedList } from '../lib/components/VirtualizedList'; +import VirtualizedList from '../lib/components/VirtualizedList'; import fakeLeaderBoardData from './fakeLeaderBoardData.json'; const StyledSpace = styled.div`