如何为新闻卡片设置移动端上滑查看详情后自动回弹的滑动效果
移动端上滑查看卡片详情实现方案
实现思路
- 基于触摸事件(touchstart/touchmove/touchend)监听用户滑动动作
- 实时控制信息栏的偏移量,实现滑动时信息栏跟随手指移动的效果
- 滑动结束后自动触发回弹复位,同时区分滑动和点击动作,避免滑动时误触发卡片跳转
- 兼容原有桌面端hover效果,不影响原有桌面端交互
代码修改
1. NewsCard.js 改动
import PropTypes from "prop-types"; import { useState, useRef } from "react"; import Image from "next/image"; import Link from "next/link"; import styles from "./NewsCard.module.scss"; import DeleteIcon from "../../public/svg/basketIcon.svg"; export default function NewsCard({ newsData, color, canDelete, onDeleteIconClick, }) { // 触摸起始Y坐标 const startY = useRef(null); // 信息栏当前偏移量 const [translateY, setTranslateY] = useState(0); // 是否为滑动操作(用于区分点击,避免误跳转) const [isSwiping, setIsSwiping] = useState(false); // 信息栏最大上滑距离,和原有hover偏移量保持一致 const MAX_OFFSET = 208; // 滑动判定阈值,移动超过5px才算滑动操作 const SWIPE_THRESHOLD = 5; const handleTouchStart = (e) => { startY.current = e.touches[0].clientY; setIsSwiping(false); }; const handleTouchMove = (e) => { if (startY.current === null) return; const currentY = e.touches[0].clientY; const deltaY = startY.current - currentY; // 仅处理上滑动作 if (deltaY > 0) { if (deltaY > SWIPE_THRESHOLD) { setIsSwiping(true); } // 限制最大上滑距离 const offset = Math.min(deltaY, MAX_OFFSET); setTranslateY(-offset); } }; const handleTouchEnd = () => { startY.current = null; // 松手后自动回弹复位 setTranslateY(0); // 延迟重置滑动状态,避免点击事件先触发 setTimeout(() => setIsSwiping(false), 300); }; const handleLinkClick = (e) => { // 滑动状态下阻止跳转 if (isSwiping) { e.preventDefault(); } }; return ( <Link href={newsData.url} passHref> <div className={styles.card} onClick={handleLinkClick}> {canDelete ? ( <div className={styles.deleteIcon}> <DeleteIcon onClick={(e) => { e.stopPropagation(); onDeleteIconClick(newsData); }} /> </div> ) : null} <Image src={newsData.imgUrl} alt="news-card" layout="fill" className={styles.image} /> <div className={`${styles.info} ${translateY < 0 ? styles.active : ''}`} style={{ background: color, transform: `translateY(${translateY}px)` }} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} > {newsData.text} </div> </div> </Link> ); } NewsCard.propTypes = { newsData: PropTypes.object, color: PropTypes.string, canDelete: PropTypes.bool, onDeleteIconClick: PropTypes.func, };
2. SCSS 改动
@import "../../styles/shared.scss"; .card { width: 350px; height: 344px; margin-right: 20px; position: relative; border-radius: 3px; cursor: pointer; overflow: hidden; touch-action: pan-y; // 优化移动端触摸体验,避免浏览器默认行为干扰 @media screen and (max-width: 1150px) { min-width: 250px; min-height: 333px; } } .info { height: 256px; top: 308px; width: 100%; position: absolute; bottom: 0; border-radius: 0px 0px 3px 3px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; font-weight: 600; font-size: 12px; line-height: 22px; letter-spacing: 0.2px; color: $color-primary-light; padding: 10px 7px 7px 6px; transition: transform 0.5s ease; // 单独指定transform的过渡,避免滑动时卡顿 } // 桌面端hover效果和移动端active效果共用样式 .info:hover, .info.active { transform: translateY(-208px); -webkit-line-clamp: 100; padding-top: 30px; } // 移动端禁用hover效果,避免点击后hover状态残留 @media screen and (max-width: 1150px) { .info:hover { transform: none; -webkit-line-clamp: 1; padding-top: 10px; } } .image { border-radius: 3px; } .deleteIcon { border: none; position: absolute; cursor: pointer; top: 6px; right: 6px; z-index: 3; display: flex; justify-content: center; align-items: center; padding: 0; svg { width: 15px; height: 15px; } border-radius: 50%; width: 25px; height: 25px; background: $color-primary-light; }
功能说明
- 桌面端保留原有的鼠标悬浮查看详情的交互逻辑
- 移动端用户手指上滑信息栏时,信息栏会跟随手指移动,松开手指后自动回弹到初始位置
- 滑动过程中不会触发卡片跳转,只有正常点击才会跳转至对应的Instagram帖子
- 滑动最大距离限制为208px,和原有桌面端悬浮展开的高度保持一致,交互体验统一
内容的提问来源于stack exchange,提问作者Andrew_25
相关产品推荐
相关产品推荐

