Widget Animation

This article explains how to create fade-in and fade-out animations from the right side for custom absolute-positioned Widgets.

The player divides Widgets into four fixed positions:

  • ⬅️ Top Left
  • ➡️ Top Right
  • ⬇️ Bottom Left
  • ↘️ Bottom Right

When the user taps the screen, if Widgets in these areas are visible, the player will automatically trigger animations to hide them.
This behavior is built-in for these four areas and applies by default to Widgets placed there.

💡 For custom Widgets with absolute positioning, you need to handle show/hide animations yourself.


Demo: Insert an Absolute Positioned Widget

We will create a demo that:

  1. Adds a button at the bottom left;
  2. On button click, inserts an absolute-positioned Widget;
  3. If the Widget already exists, clicking the button again hides it;
  4. The absolute Widget adapts to the player's animation system to fade out automatically on screen tap.

Usage Example

import React, { useEffect, useState } from 'react';
import { View } from '@ray-js/components';
import clsx from 'clsx';
import {
  IPCPlayerIntegration,
  useCtx,
  useComponentHideState,
  ComponentConfigProps,
} from '@ray-js/ipc-player-integration';
import Styles from './index.module.less';
import '@ray-js/ipc-player-integration/iconfont/iconfont.css';

function WidgetAbsolute(props: ComponentConfigProps) {
  const [clickHidden, setClickHidden] = useState(false);
  const [allComponentShouldHidden] = useComponentHideState();
  const listen = () => {
    setClickHidden(prev => !prev);
  };
  useEffect(() => {
    // Listen to the WidgetButtonClick event
    props.event.on('WidgetButtonClick', listen);
    return () => {
      props.event.off('WidgetButtonClick', listen);
    };
  }, [listen]);
  // ipc-plugin-absolute-move-in-right ipc-plugin-absolute-move-in-right-hide
  // Predefined CSS classes used to implement fade-in and fade-out animations from the right.
  return (
    <View
      className={clsx('ipc-plugin-absolute-move-in-right', {
        'ipc-plugin-absolute-move-in-right-hide': allComponentShouldHidden || clickHidden,
      })}
      style={{ width: '100px', height: '100px', backgroundColor: '#fff', color: '#ff6700' }}
    >
      1111
    </View>
  );
}

function WidgetButton(props: ComponentConfigProps) {
  const id = 'WidgetAbsolute';
  const handClick = () => {
    // When the button is clicked, check if WidgetAbsolute already exists
    // If it does, emit the WidgetButtonClick event via the event system
    // Otherwise, insert WidgetAbsolute into the player
    if (props.hasContent('absolute', id)) {
      props.event.emit('WidgetButtonClick');
    } else {
      props.addContent('absolute', {
        id: 'WidgetAbsolute',
        content: WidgetAbsolute,
        // Or write the styles into the absoluteContentClassName CSS class name
        absolutePosition: {
          position: 'absolute',
          right: '30px',
          top: '50%',
          transform: 'translateY(-50%)',
        },
      });
    }
  };
  return (
    <View onClick={handClick} style={{ color: 'var(--fontColor)' }}>
      Button
    </View>
  );
}

export default function PageHome() {
  const devId = 'devId'; // your deviceId
  const instance = useCtx({
    devId,
  });

  useEffect(() => {
    instance.addContent('bottomLeft', {
      id: 'WidgetButton',
      content: WidgetButton,
    });
  }, []);

  return (
    <View className={Styles.container}>
      <View className={Styles.playerWarp} style={{ marginTop: 300 }}>
        <IPCPlayerIntegration
          style={{ height: '300px' }}
          instance={instance}
          devId={instance.devId}
          playerFit="cover"
        />
      </View>
    </View>
  );
}