支持朋友圈图片解密;视频解密;实况渲染

This commit is contained in:
cc
2026-02-16 23:31:52 +08:00
parent 75b056d5ba
commit b4248d4a12
21 changed files with 5748 additions and 225 deletions

View File

@@ -14,12 +14,21 @@
max-height: 90vh;
object-fit: contain;
transition: transform 0.15s ease-out;
&.dragging {
transition: none;
}
}
.preview-content {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
height: fit-content;
}
.image-preview-close {
position: absolute;
bottom: 40px;
@@ -44,3 +53,38 @@
transform: translateX(-50%) scale(1.1);
}
}
.live-photo-btn {
position: absolute;
top: 15px;
right: 15px;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 16px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
cursor: pointer;
backdrop-filter: blur(10px);
transition: all 0.2s;
z-index: 10000;
&:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
}
&.active {
background: var(--accent-color, #007aff);
border-color: transparent;
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3);
}
span {
font-size: 14px;
font-weight: 500;
}
}

View File

@@ -1,36 +1,41 @@
import React, { useState, useRef, useCallback, useEffect } from 'react'
import { X } from 'lucide-react'
import { LivePhotoIcon } from './LivePhotoIcon'
import { createPortal } from 'react-dom'
import './ImagePreview.scss'
interface ImagePreviewProps {
src: string
isVideo?: boolean
liveVideoPath?: string
onClose: () => void
}
export const ImagePreview: React.FC<ImagePreviewProps> = ({ src, onClose }) => {
export const ImagePreview: React.FC<ImagePreviewProps> = ({ src, isVideo, liveVideoPath, onClose }) => {
const [scale, setScale] = useState(1)
const [position, setPosition] = useState({ x: 0, y: 0 })
const [isDragging, setIsDragging] = useState(false)
const [showLive, setShowLive] = useState(false)
const dragStart = useRef({ x: 0, y: 0 })
const positionStart = useRef({ x: 0, y: 0 })
const containerRef = useRef<HTMLDivElement>(null)
// 滚轮缩放
const handleWheel = useCallback((e: React.WheelEvent) => {
if (showLive) return // 播放实况时禁止缩放? 或者支持缩放? 暂定禁止以简化
e.preventDefault()
const delta = e.deltaY > 0 ? 0.9 : 1.1
setScale(prev => Math.min(Math.max(prev * delta, 0.5), 5))
}, [])
}, [showLive])
// 开始拖动
const handleMouseDown = useCallback((e: React.MouseEvent) => {
if (scale <= 1) return
if (showLive || scale <= 1) return
e.preventDefault()
setIsDragging(true)
dragStart.current = { x: e.clientX, y: e.clientY }
positionStart.current = { ...position }
}, [scale, position])
}, [scale, position, showLive])
// 拖动中
const handleMouseMove = useCallback((e: React.MouseEvent) => {
@@ -79,19 +84,62 @@ export const ImagePreview: React.FC<ImagePreviewProps> = ({ src, onClose }) => {
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
<img
src={src}
alt="图片预览"
className={`preview-image ${isDragging ? 'dragging' : ''}`}
<div
className="preview-content"
style={{
transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
cursor: scale > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default'
position: 'relative',
transform: `translate(${position.x}px, ${position.y}px)`,
width: 'fit-content',
height: 'fit-content'
}}
onWheel={handleWheel}
onMouseDown={handleMouseDown}
onDoubleClick={handleDoubleClick}
draggable={false}
/>
onClick={(e) => e.stopPropagation()}
>
{(isVideo || showLive) ? (
<video
src={showLive ? liveVideoPath : src}
controls={!showLive}
autoPlay
loop={showLive}
className="preview-image"
style={{
transform: `scale(${scale})`,
maxHeight: '90vh',
maxWidth: '90vw'
}}
/>
) : (
<img
src={src}
alt="图片预览"
className={`preview-image ${isDragging ? 'dragging' : ''}`}
style={{
transform: `scale(${scale})`,
maxHeight: '90vh',
maxWidth: '90vw',
cursor: scale > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default'
}}
onWheel={handleWheel}
onMouseDown={handleMouseDown}
onDoubleClick={handleDoubleClick}
draggable={false}
/>
)}
{liveVideoPath && !isVideo && (
<button
className={`live-photo-btn ${showLive ? 'active' : ''}`}
onClick={(e) => {
e.stopPropagation()
setShowLive(!showLive)
}}
title={showLive ? "显示照片" : "播放实况"}
>
<LivePhotoIcon size={20} />
<span></span>
</button>
)}
</div>
<button className="image-preview-close" onClick={onClose}>
<X size={20} />
</button>