@hyext/popup
v2.8.0
Published
A popup miniapp appearance resolution.
Downloads
106
Readme
@hyext/popup
虎牙浮窗类型小程序布局展示方案
Installation
使用前,请先向[email protected]
发送邮件(内容请带上extUuid)
申请浮窗权限。
$ npm i @hyext/popup
Usage
Configuration
首先我们需要在项目根目录的project.config.json
文件的compileNodeModules
加上@hyext/popup
字段。
{
"builder": {
"name": "@hyext/builder-beyond",
"config": {
"compileNodeModules": [
"@hyext/popup"
]
}
}
}
Create PopupLayout
首先我们需要根据我们的业务需求, 创建一个 PopupLayout,步骤如下:
import { withPopupLayout, LayoutSwitch } from '@hyext/popup'
const WidgetMeasureBox = withPopupLayout(
rootLayoutInfo => {
const isLandscape = rootLayoutInfo.isLandscape
// 例如我这个小挂件在公屏的左下角,宽高不指定,使用业务容器宽高。
return {
right: isLandscape ? 68 : 18, // 在横屏的时候距离右方68,竖屏的时候是18。
top: isLandscape ? 'center' : '68%' // 在横屏的时候垂直方向居中,竖屏的时候距离底部68%的距离。
}
},
{
defaultWidth: 95, // 为了和内部容器的hycss对齐,这里会做一层转换,以750宽度的设计稿为例,按1:1的比例widget的宽高。
defaultHeight: 95,
id: 'widget',
border: true // 调试用的, prod可以设置为false。
}
)
const FullScreenMeasureBox = withPopupLayout(
rootLayoutInfo => {
// 例如我这个内部组件需要占满全屏
return {
left: 0,
top: 0,
width: '100%',
height: '100%'
}
},
{
defaultWidth: 0, // 由于你已经把width & height设置为‘100%’, 内部容器的宽高会与rootLayoutInfo的大小一致, 所以这里设置不设置宽高都无所谓。
defaultHeight: 0,
id: 'fullScreen',
border: true // 调试用的。prod可以设置为false
}
)
Declarative
对应声明式的代码风格,我们可以接着上下文
这样使用它们:
// 连接上下文 WidgetMeasureBox FullScreenMeasureBox
const widgetLayoutName = 'widget-layout'
const fullScreenLayoutName = 'full-screen-layout'
function App() {
const [layoutName, setLayoutName] = useState(widgetLayoutName)
return (
<>
<Button onPress={() => setLayoutName(fullScreenLayoutName)}>
切换全屏
</Button>
<LayoutSwitch currentLayoutName={layoutName}>
<WidgetMeasureBox name={widgetLayoutName}>
<MyWidgetComponent></MyWidgetComponent>
</WidgetMeasureBox>
<FullScreenMeasureBox
name={fullScreenLayoutName}
>
<MyFullScreentComponent></MyFullScreentComponent>
</FullScreenMeasureBox>
</LayoutSwitch>
</>
)
}
note: LayoutSwitch只能用currentLayoutName。
Programmatic
对应编程式的代码风格,我们可以接着上下文
这样使用它们:
// 连接上下文 WidgetMeasureBox FullScreenMeasureBox
const widgetLayoutName = 'widget-layout'
const fullScreenLayoutName = 'full-screen-layout'
function App() {
return (
<>
<Button onPress={() => setLayoutName(fullScreenLayoutName)}>
切换全屏
</Button>
<LayoutSwitch defaultLayout={layoutName}>
<WidgetMeasureBox name={widgetLayoutName}>
<MyWidgetComponent></MyWidgetComponent>
</WidgetMeasureBox>
<FullScreenMeasureBox
name={fullScreenLayoutName}
>
<MyFullScreentComponent></MyFullScreentComponent>
</FullScreenMeasureBox>
</LayoutSwitch>
</>
)
}
// 重点看这部分
function MyWidgetComponent(props) {
const layout = props.layout
return (
<Button onPress={() => layout.replace(fullScreenLayoutName)}>切换回全屏</Button>
)
}
function MyFullScreentComponent(props) {
const layout = props.layout
return (
<Button onPress={() => layout.replace(widgetLayoutName)}>切换回小挂件</Button>
)
}
note: LayoutSwitch只能用defaultLayout。
Compoments
LayoutSwitch
浮窗 UI 展示控件
Props
| Name | Type | Required | Default | Description | | ----------------- | ------------------ | -------- | ------- | ---------------------------------------- | | defaultLayout | string/null | false | void | 默认 layout 的名字,使用编程式风格时存在 | | currentLayoutName | string/null | false | void | 当前 layout 名字,使用声明式风格时存在 | | children | React.ReactElement | true | void | 对应的 PopupLayout 组件 |
PopupLayout
一个 HOC 组件,当发生任何场景切换的时候(不限于横竖屏切换,业务层切换等等),负责计算浮窗位置和大小。
Props
| Name | Type | Required | Default | Description | | ---- | ------ | -------- | ------- | ---------------- | | name | string | true | void | 该 layout 的名字 |
withPopupLayout
withPopupLayout(options, hocOptions)
会创建一个PopupLayout
组件。
options 参数说明
| Name | Type | Required | Default | Description | | ---------------------- | ------------------------------------- | -------- | ------- | -------------------- | | WithPopupLayoutOptions | UserLayoutOptions/UserLayoutOptionsFN | true | void | withPopupLayout 选项 |
WithPopupLayoutOptions
可以是一个对象
UserLayoutOptions
, 含有 2 种类型属性,第一种是位置属性
,另一种是大小属性
。
位置属性
包含:top
、bottom
、left
、right
,
有 4 种组合:top
& left
、top
& right
、bottom
& left
、bottom
& right
,它们都可以接受number
or 百分比
or center
三种值。
大小属性
包含:width
、height
,
它们是非必填的,可以接受number
or 百分比
,在不填的时候,会使用内部容器的宽高作为根容器的宽高。
WithPopupLayoutOptions
亦可以是一个函数:(layout:OnLayoutChangeCallbackRes) => UserLayoutOptions
,OnLayoutChangeCallbackRes 数据结构,在 Hooks 环节中有所提及,函数返回的是UserLayoutOptions
。
hocOptions 参数说明
| Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ------------------------------------- | | defaultWidth | number | true | void | 默认业务容器的宽 | | defaultHeight | number | true | void | 默认业务容器的高 | | id | string | false | void | HOC 的 displayName | | border | boolean | false | void | HOC 内部容器是否加边框,用来 debug 的 |
PopupLayout 会向它的子组件传入一个
layout
对象,方便子组件可以使用编程式的风格操作 layout,访问方式:props.layout
,其接口如下:
layout.replace(v: string): Promise<void>
- 切换其他 layoutlayout.show(): Promise<void>
- 展示本 layoutlayout.hide(): Promise<void>
- 隐藏本 layout
其中 layout.show()
和 layout.hide()
是对于自身组件来说的展示和隐藏,只针对已渲染组件。
Hooks
useLayoutChange(deps: any[], cb: UserLayoutOptionsFN): ChangedResult
由于LayoutSwitch
的渲染方案粒度太粗,一更新就整个页面更新,而使用useLayoutChange
hook返回的状态去渲染UI会更加灵活,例如用在营收浮窗面板
,此方案更加。
demo:
// 不建议在函数内部改变 react 的 state, state 可通过 hook 返回的结果改变
function renderLayout(res: OnLayoutChangeCallbackRes) {
const { isLandscape } = res
if (isLandscape) {
return {
layout: 'landscape',
left: 'center',
top: 'center',
width: '50%',
height: 100
}
} else {
return {
layout: 'not-landscape',
left: '10%',
top: '5%',
width: '50%',
height: 100
}
}
}
function Demo() {
// deps is useEffect deps
const { layout, status, error, isLandscape } = useLayoutChange(deps, renderLayout)
// 可以利用临界状态 做一些事情 让UI切换更加平滑
if (status === 'none' || status === 'updating') return null
if (status === 'error') {
return <Abnor type="error" desc={error.message} />
}
// 可以根据用户定义的 layout 名字,映射不同的UI
switch (layout) {
case: 'not-landscape'
return <NotLandscapeUI>
case: 'landscape'
return <LandscapeUI>
default:
return null
}
}
参数说明
export type OnLayoutChangeCallbackRes = {
isLandscape: boolean
screenHeight: number
screenWidth: number
}
export type UserLayoutOptions = {
layout?: string
left?: number | string
top?: number | string
right?: number | string
bottom?: number | string
width?: number | string
height?: number | string
alpha?: number
visible?: boolean
}
export type UserLayoutOptionsFN = (
layout: OnLayoutChangeCallbackRes
) => UserLayoutOptions | Promise<UserLayoutOptions>
export type ChangeResult = {
status: LayoutStatus
layout?: string
error: null | Error
setedLayoutOptions: SetLayoutOptions
} & OnLayoutChangeCallbackRes
type LayoutStatus = 'none' | 'updating' | 'updated' | 'error'
type SetLayoutOptions = typeof hyext.panel.setLayout
useLayoutChangeWithQueue(params: UseLayoutChangeWithQueueParams): ChangedResult
和 useLayoutChange 类似, 只是加强了 NOTICE 模式的支持, 同时修改了部分API:
- 当为NOTICE时, 仅有当onNoticeShow触发后 status 才会变为 updated
- 获取setLayoutOptions的函数, 入参增加了 error, errorOpts, expUI.
- error: 初始值为null, 当发生错误时, 会被赋值为错误对象. 可以使用这个对象来达到fallback的效果. 在同一次onLayoutChange中, 最多只会尝试3次, 仍然失败后 status 变为 error
- errorOpts: 出错时的配置
- expUI: 是否应当显示实验组UI
- useLayoutChangeWithQueue 的参数改成了对象, 同时增加log参数
- useLayoutChangeWithQueue 的返回值增加了 extUI 和 onShowPayload
function renderLayout(info: GetLayoutOptsParams) {
const { isLandscape, error, errorOpts, expUI } = info
if (isLandscape) {
return {
layout: 'landscape',
left: 'center',
top: 'center',
width: '50%',
height: 100
}
} else {
// 首先尝试使用NOTICE模式
if (!error) {
return {
mode: 'NOTICE',
liveroomPopupKey: 'superfans_autotrigger',
}
} else {
// 失败则使用setLayout
return {
layout: 'not-landscape',
left: '10%',
top: '5%',
width: '50%',
height: 100
}
}
}
}
function Demo() {
// deps is useEffect deps
const { layout, status, error, isLandscape, expUI } = useLayoutChangeWithQueue({deps, getLayoutOpts: renderLayout, log: console.log})
// 可以利用临界状态 做一些事情 让UI切换更加平滑
if (status === 'none' || status === 'updating') return null
if (status === 'error') {
return <Abnor type="error" desc={error.message} />
}
// 可以根据用户定义的 layout 名字,映射不同的UI
switch (layout) {
case: 'not-landscape'
return <NotLandscapeUI>
case: 'landscape'
return <LandscapeUI>
default:
return null
}
}
参数说明
/* interface UserLayoutOptions, 除了可以透传 setMode 的参数, 还可以透传 setModeWithQueue 的参数, 如
* onShow/onNoticeHide/showTime/waitAfterResetNormal/skipResetNormal
* 详见 https://git.huya.com/exc/web/hyext-miniapp/-/tree/master/packages/hyext-utils#getsetmodewithqueuefn
*/
type GetLayoutOptsParams = OnLayoutChangeCallbackRes & { expUI: boolean, retryCount: number, errorOpts?: UserLayoutOptions, error?: Error }
interface UseLayoutChangeWithQueueParams {
getLayoutOpts: (params: GetLayoutOptsParams) => UserLayoutOptions | Promise<UserLayoutOptions>,
deps?: any[],
/* 新增入参 */
log?: (msg: string) => void
}
type ChangedResult = {
status: LayoutStatus
// 以下数据在 status 不等于 ‘none’ 时存在
error: Error | null
layout?: string
isLandscape?: boolean
screenHeight?: number
screenWidth?: number
setedLayoutOptions: {
visible: boolean
x: number
y: number
alpha: number
width: number
height: number
} | null
/* 新增数据 */
expUI: boolean // 是否展示实验组UI(当前用户属于实验组)
onShowPayload: {} // onNoticeShow 触发时携带的数据, 也可以在 getLayoutOpts 函数中透传setModeWithQueue 的onShow参数
onHidePayload: {} // onNoticeHide 触发时携带的数据, 也可以在 getLayoutOpts 函数中透传setModeWithQueue 的onNoticeHide参数
}