{"id":15287222,"url":"https://github.com/zcfan/react-spring-hippy","last_synced_at":"2026-02-18T07:31:01.188Z","repository":{"id":223803995,"uuid":"761606652","full_name":"zcfan/react-spring-hippy","owner":"zcfan","description":"react-spring target for Tencent/Hippy","archived":false,"fork":false,"pushed_at":"2024-07-16T07:12:08.000Z","size":3185,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-30T19:37:58.874Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zcfan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-22T06:27:51.000Z","updated_at":"2024-07-16T07:12:12.000Z","dependencies_parsed_at":"2024-02-22T07:20:19.580Z","dependency_job_id":"b8d24a30-248c-468d-ad30-bfa66f4cec8e","html_url":"https://github.com/zcfan/react-spring-hippy","commit_stats":null,"previous_names":["zcfan/react-spring-hippy"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zcfan/react-spring-hippy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zcfan%2Freact-spring-hippy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zcfan%2Freact-spring-hippy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zcfan%2Freact-spring-hippy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zcfan%2Freact-spring-hippy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zcfan","download_url":"https://codeload.github.com/zcfan/react-spring-hippy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zcfan%2Freact-spring-hippy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29572384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T06:19:27.422Z","status":"ssl_error","status_checked_at":"2026-02-18T06:18:44.348Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-30T15:26:35.920Z","updated_at":"2026-02-18T07:31:01.168Z","avatar_url":"https://github.com/zcfan.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-spring-hippy\n[react-spring](https://www.react-spring.dev/) target for Tencent/Hippy\n\nModified from react spring offical demo with some necessary modification to make it works in hippy:\n\n\u003e origin demo(web): https://codesandbox.io/p/sandbox/cards-stack-to6uf?file=%2Fsrc%2FApp.tsx\n\n![demo](./images/demo.gif)\n\n## 快速开始\n\n1. 安装\n\n```bash\nnpm install react-spring-hippy -D\n```\n\n2. 参考 [react-spring 文档](https://www.react-spring.dev/docs/getting-started)了解使用方式。\n\n## 支持 @hippy/react-web 转 web\n\nhippy 支持[转 web](https://hippyjs.org/#/hippy-react/web)，并且对我们这里主要用到的 setNativeProps api 做了封装。因此我们写的动画也可以正常运行在 web 页面中。\n\n但要注意只支持 View/Image/Text 这三个 hippy 组件，普通 hippy 项目中如果直接写 div/input 等 html element，也能正常运行。但 react-spring-hippy 不直接支持这些 html element。\n\n另外需要了解的是，react-spring 官方支持 web 平台（`@react-spring/web`）。如果 hippy 项目不需要支持 native 平台，完全可以直接用 `@react-spring/web` 获得最佳体验。\n\n## 前面 gif 的 demo 代码:\n\n```typescript\nimport { TouchableEvent, View } from '@hippy/react'\nimport React, { useMemo, useRef } from 'react'\nimport { animated, useSprings } from 'react-spring-hippy'\n\n// 卡片封面\nconst cards = [\n  'https://upload.wikimedia.org/wikipedia/commons/f/f5/RWS_Tarot_08_Strength.jpg',\n  'https://upload.wikimedia.org/wikipedia/commons/5/53/RWS_Tarot_16_Tower.jpg',\n  'https://upload.wikimedia.org/wikipedia/commons/9/9b/RWS_Tarot_07_Chariot.jpg',\n  'https://upload.wikimedia.org/wikipedia/commons/thumb/8/88/RWS_Tarot_02_High_Priestess.jpg/690px-RWS_Tarot_02_High_Priestess.jpg',\n  'https://upload.wikimedia.org/wikipedia/commons/d/de/RWS_Tarot_01_Magician.jpg',\n]\n\n// 用于生成每张卡片，初始飞入动画的终点\nconst to = (i: number) =\u003e ({\n  x: (750 - 250) / 2,\n  y: i * -4 + 600,\n  scale: 1,\n  rotX: `25deg`,\n  rotZ: `${-10 + Math.random() * 20}deg`,\n  delay: i * 100,\n  shadow: 0.1,\n})\n\nfunction Deck() {\n  const [props, api] = useSprings(cards.length, (i) =\u003e ({\n    ...to(i),\n    // 初始飞入动画的起点\n    from: {\n      x: (750 - 250) / 2,\n      rotX: `25deg`,\n      rotZ: '0deg',\n      scale: 1.5,\n      y: -1000,\n      shadow: 0.1,\n    },\n  }))\n\n  // 用于记录 TouchDown 时的坐标，辅助实现左右滑动卡片\n  const xRef = useRef(0)\n  // 当前是否正在触摸中\n  const downRef = useRef(false)\n  // 记录划走了几张卡片\n  const gone = useMemo(() =\u003e new Set(), [])\n\n  // 触摸事件中更新动画的逻辑\n  const update = (index, mx, down) =\u003e {\n    api.start((i) =\u003e {\n      if (index !== i) return\n      const x = (750 - 250) / 2 + mx\n      const rotZ = `${(x - (750 - 250) / 2) / 70}deg`\n      return {\n        x,\n        // react-spring 可以插值字符串\n        rotX: down ? '0deg' : '25deg',\n        rotZ,\n        scale: down ? 1.1 : 1,\n        delay: undefined,\n        config: { friction: 50, tension: down ? 800 : 500 },\n        shadow: down ? 0.4 : 0.1,\n      }\n    })\n  }\n\n  const handleTouchDown = (index: number, e: TouchableEvent\u003c'onTouchDown'\u003e) =\u003e {\n    xRef.current = e.page_x\n    downRef.current = true\n    update(index, 0, true)\n    return false\n  }\n\n  // 飞走的逻辑\n  const flyout = (index, dir) =\u003e {\n    api.start((i) =\u003e {\n      if (index !== i) return\n\n      return {\n        x: (750 + 100) * dir,\n        rotZ: `${dir * 45}deg`,\n        scale: 0.7,\n        shadow: 0.1,\n      }\n    })\n    gone.add(index)\n    if (gone.size === cards.length)\n      setTimeout(() =\u003e {\n        gone.clear()\n        api.start((i) =\u003e to(i))\n      }, 300)\n  }\n\n  const handleTouchMove = (index: number, e: TouchableEvent\u003c'onTouchMove'\u003e) =\u003e {\n    const mx = e.page_x - xRef.current\n    const down = downRef.current\n    // 触摸移动中不断更新动画\n    update(index, mx, down)\n\n    return false\n  }\n\n  const handleTouchEnd = (index: number, e: TouchableEvent\u003c'onTouchEnd'\u003e) =\u003e {\n    const mx = e.page_x - xRef.current\n    xRef.current = 0\n    downRef.current = false\n    if (Math.abs(mx) \u003e 160) {\n      flyout(index, mx \u003e 0 ? 1 : -1)\n    } else {\n      update(index, 0, false)\n    }\n    return false\n  }\n\n  return (\n    \u003c\u003e\n      {props.map(({ x, y, rotX, rotZ, scale, shadow }, i) =\u003e (\n        \u003canimated.View\n          key={i}\n          style={\n            {\n              position: 'absolute',\n              width: 250,\n              height: 440,\n              alignItems: 'center',\n              justifyContent: 'center',\n              flex: 1,\n              top: y,\n              left: x,\n            } as any\n          }\n        \u003e\n          \u003canimated.View\n            onTouchDown={(e) =\u003e handleTouchDown(i, e)}\n            onTouchMove={(e) =\u003e handleTouchMove(i, e)}\n            onTouchEnd={(e) =\u003e handleTouchEnd(i, e)}\n            style={\n              {\n                backgroundColor: 'white',\n                width: 250,\n                height: 440,\n                transform: [{ rotateX: rotX, rotateZ: rotZ }, { scale }],\n                boxShadowOpacity: shadow,\n                boxShadowRadius: 32,\n                boxShadowOffsetY: 10,\n                boxShadowColor: '#000000',\n              } as any // 刚刚跑通还有不少地方可以完善\n            }\n          \u003e\n            \u003canimated.Image\n              source={{ uri: cards[i]! }}\n              style={{\n                width: 250,\n                height: 440,\n              }}\n            /\u003e\n          \u003c/animated.View\u003e\n        \u003c/animated.View\u003e\n      ))}\n    \u003c/\u003e\n  )\n}\n\nexport default function App() {\n  return (\n    \u003cView\n      style={{\n        flex: 1,\n        alignItems: 'center',\n        justifyContent: 'center',\n        backgroundColor: 'lightblue',\n      }}\n    \u003e\n      \u003cDeck /\u003e\n    \u003c/View\u003e\n  )\n}\n```\n\n## 同步到工蜂\n\n上架内部 hippy 组件市场需要提供工蜂仓库，一个简单的同步方案：\n\n```bash\n# 1. 在 clone github 仓库后，应该有一个 origin remote\ngit remote\n# origin\n\n# 2. 给 origin remote 配置多个 push urls\ngit remote set-url --push --add origin https://zcfan@github.com/zcfan/react-spring-hippy.git\ngit remote set-url --push --add origin \u003c对应的工蜂仓库 git repo 地址\u003e\n\n# 3. 检查配置是否正确\ngit remote show origin\n# * remote origin\n#   Fetch URL: https://zcfan@github.com/zcfan/react-spring-hippy.git\n#   Push  URL: \u003c对应的工蜂仓库 git repo 地址\u003e\n#   Push  URL: https://zcfan@github.com/zcfan/react-spring-hippy.git\n\n# 4. 修改后正常 push 即可\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzcfan%2Freact-spring-hippy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzcfan%2Freact-spring-hippy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzcfan%2Freact-spring-hippy/lists"}