diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b20c345..f2ff8ca5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,6 @@ // Typescript "typescript.preferences.importModuleSpecifier": "project-relative", "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" } } diff --git a/NumberScroll.tsx b/NumberScroll.tsx new file mode 100644 index 00000000..47e921f2 --- /dev/null +++ b/NumberScroll.tsx @@ -0,0 +1,75 @@ +import React, { useEffect, useState, useRef } from 'react'; +import './NumberScroll.css'; + +interface NumberScrollProps { + endNumber: number; // 目标数字 + duration?: number; // 动画持续时间(ms) + delay?: number; // 开始延迟时间(ms) + className?: string; // 自定义样式类名 + isRunning?: boolean; // 控制是否开始滚动 +} + +export const NumberScroll: React.FC = ({ + endNumber, + duration = 2000, + delay = 0, + className = '', + isRunning = false +}) => { + const [currentNumber, setCurrentNumber] = useState(0); + const startTime = useRef(null); + const animationFrame = useRef(); + + const formatNumber = (num: number) => { + return new Intl.NumberFormat('zh-CN').format(Math.round(num)); + }; + + useEffect(() => { + // 重置状态 + startTime.current = null; + + if (!isRunning) { + if (animationFrame.current) { + cancelAnimationFrame(animationFrame.current); + } + setCurrentNumber(0); + return; + } + + const animate = (timestamp: number) => { + if (!startTime.current) startTime.current = timestamp; + + const progress = timestamp - startTime.current; + const percentage = Math.min(progress / duration, 1); + + const easeOutExpo = 1 - Math.pow(2, -10 * percentage); + const currentValue = easeOutExpo * endNumber; + + setCurrentNumber(currentValue); + + if (percentage < 1 && isRunning) { + animationFrame.current = requestAnimationFrame(animate); + } + }; + + const timer = setTimeout(() => { + animationFrame.current = requestAnimationFrame(animate); + }, delay); + + return () => { + clearTimeout(timer); + if (animationFrame.current) { + cancelAnimationFrame(animationFrame.current); + } + }; + }, [endNumber, duration, delay, isRunning]); + + // 如果不在运行状态,返回空内容 + if (!isRunning) return null; + + return ( +
+ {formatNumber(currentNumber)} +
+ ); +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..6f711638 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "VStory", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "number-flip": "^1.2.3" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://bnpm.byted.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==" + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://bnpm.byted.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/number-flip": { + "version": "1.2.3", + "resolved": "https://bnpm.byted.org/number-flip/-/number-flip-1.2.3.tgz", + "integrity": "sha512-ds88/rUo4yzcTfwWKWOepbPCbiuCL+DmFRkVSEFrQBWJqrPepU74XMaKoW9PuxzYqR7kxr8vyadMOI54XxlqbQ==", + "dependencies": { + "vue-template-compiler": "^2.7.16" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://bnpm.byted.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..05c3632a --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "number-flip": "^1.2.3" + } +} diff --git a/packages/vstory/demo/src/App.tsx b/packages/vstory/demo/src/App.tsx index 6b9821bd..4a21e5f3 100644 --- a/packages/vstory/demo/src/App.tsx +++ b/packages/vstory/demo/src/App.tsx @@ -79,6 +79,8 @@ import { TableTheme } from './demos/table/runtime/theme'; import { TableStyle } from './demos/table/runtime/style'; import { TableVisible } from './demos/table/runtime/visible'; import { SpecMarker } from './demos/chart/runtime/spec-marker'; +import { News } from './demos/works/News/News'; +import { TariffWar } from './demos/works/tariff-war'; type MenuItem = { name: string; @@ -181,6 +183,10 @@ const App = () => { name: 'VScreen', component: VScreen }, + { + name: 'News', + component: News + }, { name: 'LabelComponent', component: LabelWorks @@ -196,6 +202,10 @@ const App = () => { { name: 'NationalMemorial', component: NationalMemorial + }, + { + name: 'TariffWar', + component: TariffWar } ] }, diff --git a/packages/vstory/demo/src/demos/works/News/News.tsx b/packages/vstory/demo/src/demos/works/News/News.tsx new file mode 100644 index 00000000..4d4a0f70 --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/News.tsx @@ -0,0 +1,136 @@ +import React, { useEffect } from 'react'; +import { IChartCharacterConfig, IStoryDSL, Player, Story } from '../../../../../../vstory-core/src'; +import { registerAll } from '../../../../../src'; +import { bar1, bar1Action } from './bar1'; +import { arrow, arrowAction } from './arrow'; +import { NumberScroll } from './NumberScroll'; +import { progress, progressAction } from './progress'; + +registerAll(); + +async function loadDSL(): Promise { + const dsl: IStoryDSL = { + characters: [bar1, arrow, progress], + acts: [ + { + id: 'default-chapter', + scenes: [ + { + id: 'scene0', + actions: [bar1Action, arrowAction, progressAction] + } + ] + } + ] + }; + + return new Promise(resolve => { + const video = document.getElementById('news-video'); + if (video) { + video.addEventListener('canplay', () => { + console.log('canplay'); + resolve(dsl); + }); + } else { + resolve(dsl); // 如果没有找到视频元素,直接返回 DSL + } + }); +} + +export const News = () => { + const id = 'news'; + const videoUrl = 'https://cdn.jsdelivr.net/gh/xuanhun/articles/visactor/vstory/20250312-163446.mp4'; + const [showPlayButton, setShowPlayButton] = React.useState(true); + const [isRunning, setIsRunning] = React.useState(false); + + useEffect(() => { + const canvas = document.getElementById('news-canvas'); + const story = new Story(null, { + canvas, + width: 478, + height: 629, + background: 'rgba(0, 0, 0, 0)' + }); + + const player = new Player(story); + story.init(player); + + loadDSL().then(dsl => { + story.load(dsl); + const video: HTMLVideoElement = document.getElementById('news-video') as HTMLVideoElement; + + video.addEventListener('play', () => { + player.tickTo(0); + player.play(-1); + setTimeout(() => { + setIsRunning(true); + }, 12000); + }); + + // 添加视频结束事件监听 + video.addEventListener('ended', () => { + video.currentTime = 0; // 回到第一帧 + setShowPlayButton(true); // 显示播放按钮 + }); + }); + + return () => { + story.release(); + }; + }, []); + + return ( +
+ +
+ ); +}; diff --git a/packages/vstory/demo/src/demos/works/News/NumberScroll.css b/packages/vstory/demo/src/demos/works/News/NumberScroll.css new file mode 100644 index 00000000..fc9c8204 --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/NumberScroll.css @@ -0,0 +1,16 @@ +.number-scroll { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 2rem; + font-weight: bold; + color: #e2e7eb; + transition: color 0.3s ease; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color:rgba(0,0,0,0.5); +} + +.number-scroll.active { + color: #1890ff; +} \ No newline at end of file diff --git a/packages/vstory/demo/src/demos/works/News/NumberScroll.tsx b/packages/vstory/demo/src/demos/works/News/NumberScroll.tsx new file mode 100644 index 00000000..5fbaf316 --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/NumberScroll.tsx @@ -0,0 +1,84 @@ +import React, { useEffect, useState, useRef } from 'react'; +import './NumberScroll.css'; + +interface NumberScrollProps { + startNumber?: number; // 起始数字 + endNumber: number; // 目标数字 + duration?: number; // 动画持续时间(ms) + delay?: number; // 开始延迟时间(ms) + className?: string; // 自定义样式类名 + isRunning?: boolean; // 控制是否开始滚动 + onEnd?: () => void; // 动画结束回调 +} + +export const NumberScroll: React.FC = ({ + startNumber = 0, + endNumber, + duration = 2000, + delay = 0, + className = '', + isRunning = false, + onEnd = () => {} +}) => { + const [currentNumber, setCurrentNumber] = useState(startNumber); + const startTime = useRef(null); + const animationFrame = useRef(); + + const formatNumber = (num: number) => { + return new Intl.NumberFormat('zh-CN').format(Math.round(num)); + }; + + useEffect(() => { + // 重置状态 + startTime.current = null; + + if (!isRunning) { + if (animationFrame.current) { + cancelAnimationFrame(animationFrame.current); + } + setCurrentNumber(startNumber); + return; + } + + const animate = (timestamp: number) => { + if (!startTime.current) startTime.current = timestamp; + + const progress = timestamp - startTime.current; + const percentage = Math.min(progress / duration, 1); + + const easeOutExpo = 1 - Math.pow(2, -10 * percentage); + const currentValue = easeOutExpo * endNumber; + + setCurrentNumber(currentValue); + + if (percentage < 1 && isRunning) { + animationFrame.current = requestAnimationFrame(animate); + } else if (percentage >= 1) { + setCurrentNumber(endNumber); + // 动画完成时调用onEnd回调 + onEnd(); + } + }; + + const timer = setTimeout(() => { + animationFrame.current = requestAnimationFrame(animate); + }, delay); + + return () => { + clearTimeout(timer); + if (animationFrame.current) { + cancelAnimationFrame(animationFrame.current); + } + }; + }, [endNumber, duration, delay, isRunning, onEnd]); + + // 如果不在运行状态,返回空内容 + if (!isRunning) return null; + + return ( +
+ £ + {formatNumber(currentNumber)} +
+ ); +}; diff --git a/packages/vstory/demo/src/demos/works/News/arrow.tsx b/packages/vstory/demo/src/demos/works/News/arrow.tsx new file mode 100644 index 00000000..ccbbc7ee --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/arrow.tsx @@ -0,0 +1,60 @@ +export const arrow = { + type: 'Image', + id: 'arrowImg', + zIndex: 1, + position: { + top: 478 / 2 + 100, + left: 629 / 2 - 130, + width: 110, + height: 110, + angle: -Math.PI / 4 + }, + + options: { + graphic: { + stroke: false, + + image: + 'data:image/png;base64,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' + } + } +}; + +export const arrowAction = { + characterId: 'arrowImg', + characterActions: [ + { + action: 'appear', + startTime: 7200, + payload: [ + { + animation: { + duration: 2800, + easing: 'linear', + effect: 'wipe' + } as any + } + ] + }, + { + action: 'appear', + startTime: 7200, + payload: [ + { + animation: { + duration: 2800, + easing: 'linear', + effect: 'wipe' + } as any + } + ] + }, + { + action: 'disappear', + startTime: 10000, + payload: { + animation: { duration: 400 } + } + } + ] +}; diff --git a/packages/vstory/demo/src/demos/works/News/bar1.tsx b/packages/vstory/demo/src/demos/works/News/bar1.tsx new file mode 100644 index 00000000..df89ec85 --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/bar1.tsx @@ -0,0 +1,96 @@ +import { IChartCharacterConfig } from '../../../../../../vstory-core/src'; +export const bar1: IChartCharacterConfig = { + type: 'VChart', + id: 'bar1', + zIndex: 1, + options: { + panel: { + fill: 'rgba(100, 76, 76, 0.5)' + }, + spec: { + type: 'bar', + xField: 'name', + yField: 'value', + + label: { + visible: true, + position: 'middle', + style: { + fill: 'black', + fontSize: 12 + } + }, + data: { + id: 'bar1Data', + values: [ + { + name: '2006', + value: 2.3 + }, + { + name: '2007', + value: 0 + } + ] + }, + axes: [ + { + orient: 'bottom', + label: { + style: { + fill: 'yellow' + } + } + } + ] + } + }, + position: { + top: 478 / 2 + 100, + left: 629 / 2 - 200, + width: 210, + height: 210 + } +}; + +export const bar1Action = { + characterId: 'bar1', + characterActions: [ + { + action: 'appear', + startTime: 7100, + payload: [ + { + animation: { duration: 200 } + } + ] + }, + + { + action: 'update', + startTime: 7200, + payload: { + id: 'bar1Data', + values: [ + { + name: '2006', + value: 2.3 + }, + { + name: '2007', + value: 2.5 + } + ], + + animation: { duration: 2800 } + } + }, + { + action: 'disappear', + startTime: 10000, + payload: { + animation: { duration: 400 } + } + } + ] +}; diff --git a/packages/vstory/demo/src/demos/works/News/line.ts b/packages/vstory/demo/src/demos/works/News/line.ts new file mode 100644 index 00000000..003f917c --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/line.ts @@ -0,0 +1,185 @@ +import { IChartCharacterConfig } from '../../../../../../vstory-core/src'; + +export const line: IChartCharacterConfig = { + type: 'VChart', + id: 'line', + zIndex: 1, + position: { + top: 629 / 2 - 100, + left: 478 / 2 - 100, + width: 200, + height: 200 + }, + options: { + panel: { + fill: 'rgba(100, 76, 76, 0.5)' + }, + + spec: { + type: 'line', + data: { + id: 'data', + values: [ + { + time: '2:00', + value: 0.1 + }, + { + time: '2:10', + value: 0.2 + }, + { + time: '2:20', + value: 0.3 + }, + { + time: '2:30', + value: 0.4 + }, + { + time: '2:40', + value: 0.5 + }, + { + time: '2:50', + value: 0.6 + }, + { + time: '3:00', + value: 0.7 + }, + { + time: '3:10', + value: 0.8 + }, + { + time: '3:20', + value: 0.9 + }, + { + time: '3:30', + value: 1.0 + }, + { + time: '3:40', + value: 1.1 + }, + { + time: '3:50', + value: 1.2 + }, + { + time: '4:00', + value: 1.3 + }, + { + time: '4:10', + value: 1.4 + }, + { + time: '4:20', + value: 1.5 + }, + { + time: '4:30', + value: 1.6 + }, + { + time: '4:40', + value: 1.7 + }, + { + time: '4:50', + value: 1.8 + }, + { + time: '5:00', + value: 1.9 + }, + { + time: '5:10', + value: 2.0 + }, + { + time: '5:20', + value: 2.1 + }, + { + time: '5:30', + value: 2.2 + }, + { + time: '5:40', + value: 2.3 + }, + { + time: '5:50', + value: 2.4 + }, + { + time: '6:00', + value: 2.5 + }, + { + time: '6:10', + value: 2.6 + }, + { + time: '6:20', + value: 2.7 + }, + { + time: '6:30', + value: 2.8 + }, + { + time: '6:40', + value: 2.9 + }, + { + time: '6:50', + value: 3.0 + } + ] + }, + xField: 'time', + yField: 'value', + //隐藏x轴 + axes: [ + { + orient: 'bottom', + visible: false + } + ], + + line: { + style: { + curveType: 'monotone' + } + } + } + } +}; + +export const lineAction = { + characterId: 'line', + characterActions: [ + { + action: 'appear', + startTime: 1000, + payload: [ + { + animation: { duration: 1500 } + } + ] + }, + + { + // action: 'disappear', + // startTime:2000, + // payload: { + // animation: { duration: 400, } + // } + } + ] +}; diff --git a/packages/vstory/demo/src/demos/works/News/progress.ts b/packages/vstory/demo/src/demos/works/News/progress.ts new file mode 100644 index 00000000..bad89ce4 --- /dev/null +++ b/packages/vstory/demo/src/demos/works/News/progress.ts @@ -0,0 +1,142 @@ +import { IChartCharacterConfig } from '../../../../../../vstory-core/src'; + +export const progress: IChartCharacterConfig = { + type: 'VChart', + id: 'progress', + zIndex: 1, + position: { + top: 629 / 2 + 80, + left: 478 / 2 - 478 / 4, + width: 478 / 2, + height: 80 + }, + options: { + panel: { + fill: 'rgba(100, 76, 76, 0.5)' + }, + + spec: { + type: 'linearProgress', + data: [ + { + id: 'progressData', + values: [ + { + type: 'Tradition Industries', + value: 0.5, + text: '0.5' + } + ] + } + ], + direction: 'horizontal', + xField: 'value', + yField: 'type', + seriesField: 'type', + + cornerRadius: 20, + bandWidth: 30, + extensionMark: [ + { + type: 'rule', + dataId: 'progressData', + visible: true, + style: { + x: (datum, ctx, elements, dataView) => { + return ctx.valueToX([0.3]); + }, + y: (datum, ctx, elements, dataView) => { + return ctx.valueToY([datum.type]) - 15; + }, + x1: (datum, ctx, elements, dataView) => { + return ctx.valueToX([0.3]); + }, + y1: (datum, ctx, elements, dataView) => { + return ctx.valueToY([datum.type]) + 15; + }, + stroke: '#fff', + lineWidth: 4, + zIndex: 1 + } + }, + { + type: 'rule', + dataId: 'progressData', + visible: true, + style: { + x: (datum, ctx, elements, dataView) => { + return ctx.valueToX([0.5]); + }, + y: (datum, ctx, elements, dataView) => { + return ctx.valueToY([datum.type]) - 15; + }, + x1: (datum, ctx, elements, dataView) => { + return ctx.valueToX([0.5]); + }, + y1: (datum, ctx, elements, dataView) => { + return ctx.valueToY([datum.type]) + 15; + }, + stroke: '#fff', + lineWidth: 4, + zIndex: 1 + } + } + ], + axes: [ + { + orient: 'bottom', + type: 'linear', + visible: true, + grid: { + visible: false + }, + label: { + flush: true, + style: { + fill: 'white' + } + } + } + ] + } + } +}; + +export const progressAction = { + characterId: 'progress', + characterActions: [ + { + action: 'appear', + startTime: 20000, + payload: [ + { + animation: { duration: 0 } + } + ] + }, + + { + action: 'update', + startTime: 21000, + payload: { + id: 'progressData', + values: [ + { + type: 'Tradition Industries', + value: 0.3, + text: '0.3' + } + ], + + animation: { duration: 2000 } + } + }, + { + action: 'disappear', + startTime: 23000, + payload: { + animation: { duration: 400 } + } + } + ] +}; diff --git a/packages/vstory/demo/src/demos/works/tariff-war.tsx b/packages/vstory/demo/src/demos/works/tariff-war.tsx new file mode 100644 index 00000000..be65551b --- /dev/null +++ b/packages/vstory/demo/src/demos/works/tariff-war.tsx @@ -0,0 +1,1571 @@ +import React, { createRef, useEffect } from 'react'; +import { Player, Story, initVR, registerGraphics, registerCharacters } from '../../../../../vstory-core/src'; +import { registerVComponentAction, registerVChartAction } from '../../../../../vstory-player/src'; + +registerGraphics(); +registerCharacters(); +registerVChartAction(); +registerVComponentAction(); +initVR(); +const actionShow = { + characterId: [ + 'background-top', + 'background-bottom-filter', + 'background-bottom-left', + 'background-bottom-right', + 'Title', + 'SplitLine', + 'Star', + 'LeftPercent', + 'LeftDescription', + 'RightPercent', + 'RightDescription', + + 'chart', + 'chart2' + ], + characterActions: [ + { + action: 'appear', + startTime: 0, + payload: { + animation: { + duration: 0 + } + } + } + ] +}; + +const actionShowTile = { + characterId: ['Title'], + characterActions: [ + { + action: 'style', + startTime: 1000, + payload: { + animation: { + duration: 2000, + easing: 'quadInOut' + }, + graphic: { fontSize: 200 } + } + } + ] +}; +const actionScaleTitle = { + characterId: ['Title'], + characterActions: [ + { + action: 'style', + startTime: 3500, + payload: { + animation: { + duration: 1000, + easing: 'quadInOut', + effect: 'fade' + }, + + graphic: { fontSize: 100 } + } + } + ] +}; + +const actionMoveTitle = { + characterId: ['Title'], + characterActions: [ + { + action: 'moveTo', + startTime: 4500, + payload: { + destination: { + x: 1920 / 2, + y: 100 + }, + animation: { + duration: 800, + easing: 'quadInOut' + } + } + } + ] +}; + +const actionScaleLeftPercent = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 5500, + payload: { + animation: { + duration: 100, + easing: 'quadInOut', + effect: 'fade' + }, + + graphic: { fontSize: 10 } + } + } + ] +}; +const actionScaleLeftPercent1 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 5600, + payload: { + animation: { + duration: 300, + easing: 'quadIn', + effect: 'fade' + }, + text: { text: '看我致命一击!' }, + graphic: { + fontSize: 100 + } + } + } + ] +}; + +const actionUpdateChart1to10 = { + characterId: ['chart'], + characterActions: [ + { + action: 'update', + startTime: 6000, + payload: { + id: '0', + values: [ + { + state: '美国', + value: 10 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +const actionUpdateRightPercentColor = { + characterId: ['RightPercent'], + characterActions: [ + { + action: 'style', + startTime: 7000, + payload: { + animation: { + duration: 300 + }, + graphic: { + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 0, + y1: 1, + stops: [ + { + offset: 0, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.8, + color: '#48A0CF' // 0% 处的颜色 + }, + + { + offset: 1, + color: 'red' // 100% 处的颜色 + } + ] + } + } + } + } + ] +}; +const actionUpdateChart2to10 = { + characterId: ['chart2'], + characterActions: [ + { + action: 'update', + startTime: 7300, + payload: { + id: '0', + values: [ + { + state: '中国', + value: 10 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +const actionMoveLeftPercent = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 8800, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; +const actionUpdateLeftPercent2 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 8900, + payload: { + text: { text: '看我致命二击!' }, + animation: { + duration: 0 + } + } + } + ] +}; +const actionMoveLeftPercent2 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 8900, + payload: { + text: { text: '看我致命二击!' }, + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 400, + easing: 'quadIn' + } + } + } + ] +}; + +const actionUpdateChart1to34 = { + characterId: ['chart'], + characterActions: [ + { + action: 'update', + startTime: 9500, + payload: { + id: '0', + values: [ + { + state: '美国', + value: 34 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; +const actionUpdateRightPercentColor2 = { + characterId: ['RightPercent'], + characterActions: [ + { + action: 'style', + startTime: 11000, + payload: { + animation: { + duration: 300 + }, + graphic: { + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 0, + y1: 1, + stops: [ + { + offset: 0, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.6, + color: '#48A0CF' // 0% 处的颜色 + }, + + { + offset: 1, + color: 'red' // 100% 处的颜色 + } + ] + } + } + } + } + ] +}; + +const actionUpdateChart2to34 = { + characterId: ['chart2'], + characterActions: [ + { + action: 'update', + startTime: 11500, + payload: { + id: '0', + values: [ + { + state: '中国', + value: 34 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +const actionMoveLeftPercent3 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 13000, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; +const actionUpdateLeftPercent3 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 13100, + payload: { + text: { text: '看我致命三击!' }, + animation: { + duration: 0 + } + } + } + ] +}; +const actionMoveLeftPercent4 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 13100, + payload: { + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 400, + easing: 'quadIn' + } + } + } + ] +}; + +const actionUpdateChart1to104 = { + characterId: ['chart'], + characterActions: [ + { + action: 'update', + startTime: 13500, + payload: { + id: '0', + values: [ + { + state: '美国', + value: 104 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +const actionUpdateRightPercentColor3 = { + characterId: ['RightPercent'], + characterActions: [ + { + action: 'style', + startTime: 14500, + payload: { + animation: { + duration: 300 + }, + graphic: { + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 0, + y1: 1, + stops: [ + { + offset: 0, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.4, + color: '#48A0CF' // 0% 处的颜色 + }, + + { + offset: 1, + color: 'red' // 100% 处的颜色 + } + ] + } + } + } + } + ] +}; + +const actionUpdateChart2to84 = { + characterId: ['chart2'], + characterActions: [ + { + action: 'update', + startTime: 15000, + payload: { + id: '0', + values: [ + { + state: '中国', + value: 84 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +// 第四回合 +const actionMoveLeftPercent5 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 16000, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; +const actionUpdateLeftPercent5 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 16100, + payload: { + text: { text: '看我致命四击!' }, + animation: { + duration: 0 + } + } + } + ] +}; +const actionMoveLeftPercent6 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 16100, + payload: { + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 400, + easing: 'quadIn' + } + } + } + ] +}; + +const actionUpdateChart1to145 = { + characterId: ['chart'], + characterActions: [ + { + action: 'update', + startTime: 16500, + payload: { + id: '0', + values: [ + { + state: '美国', + value: 145 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +const actionUpdateRightPercentColor4 = { + characterId: ['RightPercent'], + characterActions: [ + { + action: 'style', + startTime: 16500, + payload: { + animation: { + duration: 300 + }, + graphic: { + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 0, + y1: 1, + stops: [ + { + offset: 0, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.2, + color: '#48A0CF' // 0% 处的颜色 + }, + + { + offset: 1, + color: 'red' // 100% 处的颜色 + } + ] + } + } + } + } + ] +}; + +const actionUpdateChart2to125 = { + characterId: ['chart2'], + characterActions: [ + { + action: 'update', + startTime: 17000, + payload: { + id: '0', + values: [ + { + state: '中国', + value: 125 + } + ], + + animation: { duration: 1000 } + } + } + ] +}; + +// 第5回合 + +const actionMoveLeftPercent7 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 18000, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; +const actionUpdateLeftPercent7 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 18100, + payload: { + text: { text: '看我!' }, + animation: { + duration: 0 + } + } + } + ] +}; +const actionMoveLeftPercent8 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 18100, + payload: { + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 400, + easing: 'quadIn' + } + } + } + ] +}; +const actionMoveLeftPercent9 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 18500, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; + +const actionMoveLeftPercent10 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 18500, + payload: { + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 400, + easing: 'quadIn' + } + } + } + ] +}; + +const actionMoveLeftPercent11 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 18900, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; +const actionUpdateLeftPercent8 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 19000, + payload: { + text: { text: '看我!快看我!' }, + animation: { + duration: 0 + } + } + } + ] +}; +const actionMoveLeftPercent12 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 19000, + payload: { + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 400, + easing: 'quadIn' + } + } + } + ] +}; +const actionUpdateRightPercentCotent = { + characterId: ['RightPercent'], + characterActions: [ + { + action: 'style', + startTime: 19500, + payload: { + text: { text: '嗯!看着呢' }, + animation: { + duration: 300 + }, + graphic: { + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 0, + y1: 1, + stops: [ + { + offset: 0, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.2, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.4, + color: 'blue' // 0% 处的颜色 + }, + { + offset: 1, + color: 'red' // 100% 处的颜色 + } + ] + } + } + } + } + ] +}; + +// 第六回合 +const actionMoveLeftPercent13 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 21000, + payload: { + destination: { + x: -1920 / 2, + y: 820 + }, + animation: { + duration: 100, + easing: 'quadInOut' + } + } + } + ] +}; +const actionUpdateLeftPercent9 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'style', + startTime: 21100, + payload: { + text: { text: '部分电子产品免收高关税' }, + graphic: { + fontSize: 20 + }, + animation: { + duration: 0 + } + } + } + ] +}; +const actionMoveLeftPercent14 = { + characterId: ['LeftPercent'], + characterActions: [ + { + action: 'moveTo', + startTime: 21100, + payload: { + destination: { + x: 160 + (1920 / 2 - 160) / 2, + y: 820 + }, + animation: { + duration: 2000 + } + } + } + ] +}; + +const actionUpdateRightPercentCotent2 = { + characterId: ['RightPercent'], + characterActions: [ + { + action: 'style', + startTime: 23200, + payload: { + text: { text: '👍🏻看见了' }, + animation: { + duration: 300 + }, + graphic: { + fill: { + gradient: 'linear', + x0: 0, + y0: 0, + x1: 0, + y1: 1, + stops: [ + { + offset: 0, + color: '#48A0CF' // 0% 处的颜色 + }, + { + offset: 0.2, + color: '#48A0CF' // 0% 处的颜色 + }, + + { + offset: 1, + color: 'red' // 100% 处的颜色 + } + ] + } + } + } + } + ] +}; +//hide all +const actionHideAll = { + characterId: [ + 'LeftPercent', + 'LeftDescription', + 'RightPercent', + 'RightDescription', + 'background-bottom-filter', + 'chart', + 'chart2', + 'Star', + 'SplitLine' + ], + characterActions: [ + { + action: 'disappear', + startTime: 24500, + payload: { + animation: { + duration: 500, + easing: 'quadInOut', + effect: 'fade' + } + } + } + ] +}; +// scale china + +const actionScaleRight = { + characterId: ['background-bottom-right'], + characterActions: [ + { + action: 'style', + payload: { + graphic: { + width: 1920 - 100 + }, + animation: { + duration: 0 + } + }, + + startTime: 24999 + } + ] +}; +const actionMoveRight = { + characterId: ['background-bottom-right'], + characterActions: [ + { + action: 'moveTo', + payload: { + destination: { + x: 100, + y: 0 + }, + animation: { + duration: 3000, + easing: 'linear' + } + }, + + startTime: 25000 + } + ] +}; +const actionScaleLeft = { + characterId: ['background-bottom-left'], + characterActions: [ + { + action: 'style', + payload: { + graphic: { + width: 100 + }, + + animation: { + duration: 3000, + easing: 'linear' + } + }, + + startTime: 25000 + } + ] +}; + +const actionUpdateTitleSize = { + characterId: ['Title'], + characterActions: [ + { + action: 'style', + startTime: 25000, + payload: { + text: { text: '中国必胜!' }, + graphic: { + fill: 'red' + }, + animation: { + duration: 500 + } + } + } + ] +}; + +const actionMoveTitle2 = { + characterId: ['Title'], + characterActions: [ + { + action: 'moveTo', + payload: { + destination: { + x: 1920 / 2, + y: 1080 / 2 + }, + animation: { + duration: 2500, + easing: 'quadInOut' + } + }, + + startTime: 25500 + } + ] +}; +const actionUpdateTitle2 = { + characterId: ['Title'], + characterActions: [ + { + action: 'style', + payload: { + graphic: { + fontSize: 300 + }, + + animation: { + duration: 2500, + easing: 'quadInOut' + } + }, + + startTime: 25500 + } + ] +}; +async function loadDSL() { + return { + characters: [ + { + type: 'Rect', + id: 'background-top', + zIndex: 2, + position: { + top: 0, + left: 0, + width: 1920, + height: 254 + }, + options: { + graphic: { + fill: '#2D6BA0', + stroke: false + } + } + }, + { + type: 'Rect', + id: 'background-bottom-filter', + zIndex: 2, + position: { + top: 0, + left: 0, + width: 1920, + height: 1080 + }, + options: { + graphic: { + fill: '#193446', + fillOpacity: 0.6, + stroke: false + } + } + }, + { + type: 'Image', + id: 'background-bottom-left', + zIndex: 1, + position: { + top: 0, + left: 0, + width: 1920 / 2, + height: 1080 + }, + options: { + graphic: { + image: `https://cdn.jsdelivr.net/gh/xuanhun/articles/visactor/img/infographic/trump.png` + } + } + }, + { + type: 'Image', + id: 'background-bottom-right', + zIndex: 1, + position: { + top: 0, + left: 1920 / 2, + width: 1920 / 2, + height: 1080 + }, + options: { + graphic: { + image: `https://cdn.jsdelivr.net/gh/xuanhun/articles/visactor/img/infographic/china.png` + } + } + }, + { + type: 'Text', + id: 'Title', + zIndex: 3, + position: { + top: (1080 - 200) / 2, + left: 1920 / 2, + width: 1920, + height: 600 + }, + options: { + graphic: { + fontSize: 2, + + textAlign: 'center', + textBaseline: 'middle', + stroke: 'white', + + fill: 'black', + fontWeight: 'bolder', + lineWidth: 10, + textConfig: [ + { + text: '中美关税对决!', + textAlign: 'center' + } + ] + } + } + }, + + { + type: 'Line', + id: 'SplitLine', + zIndex: 3, + position: { + top: 340, + left: 1920 / 2, + width: 20, + height: 560 + }, + options: { + graphic: { + stroke: '#48A0CF', + lineWith: 10, + points: [ + { x: 0, y: 0 }, + { x: 0, y: 560 } + ] + } + } + }, + { + type: 'Shape', + id: 'Star', + zIndex: 3, + position: { + top: 340 + 560 - 70, + left: 1920 / 2 - 70, + width: 140, + height: 140 + }, + options: { + graphic: { + fill: '#48A0CF', + stroke: false, + symbolType: + 'M 0.63 -1.1 c -0.61 1.06 -0.64 1.06 -1.25 0 c 0.61 1.06 0.6 1.08 -0.63 1.08 c 1.22 0 1.24 0.02 0.63 1.08 c 0.61 -1.06 0.64 -1.06 1.25 0 c -0.61 -1.06 -0.6 -1.08 0.63 -1.08 C 0.03 -0.01 0.01 -0.04 0.63 -1.1 z', + size: 140 + } + } + }, + { + type: 'Text', + id: 'LeftPercent', + zIndex: 3, + position: { + top: 820, + left: 160 + (1920 / 2 - 160) / 2, + width: 600, + height: 160 + }, + options: { + graphic: { + text: '暗中观察', + fill: '#48A0CF', + textAlign: 'center', + textBaseline: 'middle', + fontSize: 100, + fontWeight: 600 + } + } + }, + { + type: 'Text', + id: 'LeftDescription', + zIndex: 3, + position: { + top: 920, + left: 160 + (1920 / 2 - 160) / 2, + width: 460, + height: 108 + }, + options: { + graphic: { + fill: 'white', + fontSize: 30, + textAlign: 'center', + textBaseline: 'middle', + width: 460, + height: 108, + wordBreak: 'break-word', + textConfig: [ + { + text: '美国对中国加增关税' + } + ] + } + } + }, + { + type: 'Text', + id: 'RightPercent', + zIndex: 3, + position: { + top: 820, + left: 1920 / 2 + (1920 / 2 - 160) / 2, + width: 600, + height: 160 + }, + options: { + graphic: { + text: '从容应对', + fill: '#48A0CF', + textAlign: 'center', + textBaseline: 'middle', + fontSize: 110, + fontWeight: 600, + stroke: '#48A0CF' + } + } + }, + { + type: 'Text', + id: 'RightDescription', + zIndex: 3, + position: { + top: 920, + left: 1920 / 2 + (1920 / 2 - 160) / 2, + width: 460, + height: 108 + }, + options: { + graphic: { + fill: 'white', + fontSize: 30, + width: 460, + height: 108, + wordBreak: 'break-word', + textAlign: 'center', + textBaseline: 'middle', + textConfig: [ + { + text: '中国对美国加增关税' + } + ] + } + } + }, + { + id: 'chart', + type: 'VChart', + zIndex: 2, + position: { + top: 254, + left: 160, + width: 1920 / 2 - 160, + height: 540 + }, + options: { + spec: { + type: 'common', + animation: false, + barWidth: 0.1, + series: [ + { + type: 'bar', + xField: ['state'], + yField: 'value', + seriesField: 'state', + direction: 'vertical', + stack: true, + dataId: '0', + + label: { + visible: true, + formatter: `{value}%`, + offset: 10, + overlap: { clampForce: false }, + style: { + stroke: 'white', + lineWidth: 5, + fill: 'black', + fontWeight: 'bold', + fontSize: 36 + } + } + } + ], + data: [ + { + id: '0', + values: [ + { + state: '美国', + value: 0 + } + ] + } + ], + color: ['#222A5A'], + axes: [ + { + orient: 'bottom', + visible: true, + paddingOuter: [0.1], + label: { + visible: false + }, + grid: { + visible: false + }, + tick: { + visible: false + }, + domainLine: { + visible: true + } + }, + { + label: { + visible: false + }, + domainLine: { + visible: false + }, + tick: { visible: false, tickStep: 10 }, + range: { max: 150 }, + orient: 'left' + }, + { + label: { + visible: true, + style: { + fill: 'rgb(246, 237, 237)', + fontSize: 30 + } + }, + domainLine: { + visible: false + }, + tick: { visible: false, tickStep: 10 }, + range: { max: 130 }, + orient: 'right' + } + ] + } + } + }, + + { + id: 'chart2', + type: 'VChart', + zIndex: 2, + position: { + top: 254, + left: 1920 / 2, + width: 1920 / 2 - 160, + height: 540 + }, + options: { + spec: { + type: 'common', + animation: false, + barWidth: 10, + series: [ + { + type: 'bar', + xField: ['state'], + yField: 'value', + seriesField: 'state', + direction: 'vertical', + stack: true, + dataId: '0', + + label: { + visible: true, + formatter: `{value}%`, + offset: 10, + overlap: { clampForce: false }, + style: { + stroke: 'white', + lineWidth: 5, + fill: 'black', + fontWeight: 'bold', + fontSize: 36 + } + } + } + ], + data: [ + { + id: '0', + values: [ + { + state: '中国', + value: 0 + } + ] + } + ], + color: ['red'], + axes: [ + { + orient: 'bottom', + visible: true, + paddingOuter: [0.1], + label: { + visible: false + }, + grid: { + visible: false + }, + tick: { + visible: false + }, + domainLine: { + visible: false + } + }, + { + label: { + visible: true, + style: { + fill: 'rgb(246, 237, 237)', + fontSize: 30 + } + }, + domainLine: { + visible: false + }, + tick: { visible: false, tickStep: 10 }, + range: { max: 150 }, + orient: 'left' + } + ] + } + } + } + ], + acts: [ + { + id: 'page1', + scenes: [ + { + id: 'singleScene', + actions: [ + actionShow, + actionShowTile, + actionScaleTitle, + actionMoveTitle, + actionScaleLeftPercent, + actionScaleLeftPercent1, + actionUpdateChart1to10, + actionUpdateRightPercentColor, + actionUpdateChart2to10, + actionMoveLeftPercent, + actionUpdateLeftPercent2, + actionMoveLeftPercent2, + actionUpdateChart1to34, + actionUpdateRightPercentColor2, + actionUpdateChart2to34, + actionMoveLeftPercent3, + actionUpdateLeftPercent3, + actionMoveLeftPercent4, + actionUpdateChart1to104, + actionUpdateRightPercentColor3, + actionUpdateChart2to84, + actionMoveLeftPercent5, + actionUpdateLeftPercent5, + actionMoveLeftPercent6, + actionUpdateChart1to145, + actionUpdateRightPercentColor4, + actionUpdateChart2to125, + actionMoveLeftPercent7, + actionUpdateLeftPercent7, + actionMoveLeftPercent8, + actionMoveLeftPercent9, + actionMoveLeftPercent10, + actionMoveLeftPercent11, + actionUpdateLeftPercent8, + actionMoveLeftPercent12, + actionUpdateRightPercentCotent, + actionMoveLeftPercent13, + actionUpdateLeftPercent9, + actionMoveLeftPercent14, + actionUpdateRightPercentCotent2, + actionHideAll, + actionScaleLeft, + actionScaleRight, + actionMoveRight, + actionUpdateTitleSize, + actionMoveTitle2, + actionUpdateTitle2 + ] + } + ] + } + ] + }; +} + +export const TariffWar = () => { + const id = 'TariffWar'; + + useEffect(() => { + const container = document.getElementById(id); + const canvas = document.createElement('canvas'); + container?.appendChild(canvas); + + const story = new Story(null, { canvas, width: 1000, height: 500, scaleX: 0.5, scaleY: 0.5 }); + const player = new Player(story); + story.init(player); + + loadDSL().then(dsl => { + story.load(dsl); + player.play(); + }); + + console.log(story); + + return () => { + story.release(); + }; + }, []); + + return
; +}; diff --git a/yarn.lock b/yarn.lock index fb57ccd1..ca150175 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,3 +2,27 @@ # yarn lockfile v1 +de-indent@^1.0.2: + version "1.0.2" + resolved "https://bnpm.byted.org/de-indent/-/de-indent-1.0.2.tgz" + integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== + +he@^1.2.0: + version "1.2.0" + resolved "https://bnpm.byted.org/he/-/he-1.2.0.tgz" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +number-flip@^1.2.3: + version "1.2.3" + resolved "https://bnpm.byted.org/number-flip/-/number-flip-1.2.3.tgz" + integrity sha512-ds88/rUo4yzcTfwWKWOepbPCbiuCL+DmFRkVSEFrQBWJqrPepU74XMaKoW9PuxzYqR7kxr8vyadMOI54XxlqbQ== + dependencies: + vue-template-compiler "^2.7.16" + +vue-template-compiler@^2.7.16: + version "2.7.16" + resolved "https://bnpm.byted.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz" + integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ== + dependencies: + de-indent "^1.0.2" + he "^1.2.0"