整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

「CSS」 position:fixed 定位实现 模态框弹窗浮动垂直水平居中

flex 模态框弹窗浮动垂直水平居中

- position:fixed 定位

- 元素的位置相对于浏览器窗口是固定位置。

- 即使窗口是滚动的它也不会移动;

HTML 代码实例

```

前的文章讲了可视窗口可改变位置大小(查看),本文介绍配合react-draggable来实现既可改变大小又可移动位置的模态窗口实现。

实现后效果:

<script src="https://lf6-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>

该实现过程旨在它拖拽改变大小和改变位置抽象出来,所以具体的布局由调用方来处理,后面会有使用示例。

可拖拽窗口(DragableWindow)

import React, {createRef, useEffect,useState } from 'react'
import Drag,{ControlPosition } from 'react-draggable';
import {Resizable} from 're-resizable';
import {resizeOnVertical} from './utils'
import useStyle from './style'
import {calculatePositionAlwaysShowInView} from './utils'
interface Point extends ControlPosition{
}
interface DragabeWindowProps{
    width:number;
    height:number;
    children?:any;
    dragContorlClassName?:string;
}
const DragableWindow : React.FC<DragabeWindowProps> = (props) =>{
    const CONTAINER_MIN_HEIGHT = 504;
    const {children}  = props;
    const [resizePosition,setResizePosition] = useState<Point>({x:0,y:0});
    const [position,setPosition] = useState<Point|null>(); 
    const [containerHeight,setContainerHeight] = useState<number>(CONTAINER_MIN_HEIGHT);
     /**
	 * @param e {object} 事件源
	 * @param direction {string} 拖动方向
	 * @param ref {dom} 拖动的元素
	 * @param d {object} 移动偏移量
	 */
	const onResize = (e:any, direction:any, ref:any, d:any) => {
		/* resize 之前的值 */
		let originX = resizePosition?resizePosition.x:0;
		let originY = resizePosition?resizePosition.y:0;

		/* 移动的位移 */
		let moveW = d.width;
		let moveH = d.height;

		/* 移动的位移 */
		let x = null;
		let y = null;

		/* 处理上边缘 */
		if (/left/i.test(direction)) {
			x = originX - moveW;
			y = originY;
			setPosition({ x, y });

			/* 处理左边缘 */
		} else if (/top/i.test(direction)) {
			x = originX;
			y = originY - moveH;
			setPosition({ x, y });
		} else {
			setPosition(null);
		}

		if (x || y) {
			ref.style.transform = `translate(${x}px, ${y}px)`;
		}
	}

    const onResizeStop = (e:any, direction:any, ref:any, d:any) => {
		if (position) {
			setResizePosition(position);
		}
        if (resizeOnVertical(direction)) {
            //setContainerHeight(containerHeight + d.height);
		}
	}
    /**弹出窗口的蒙层样式 */
    const popContainer: React.CSSProperties = {
        top: 0,
        left: 0,
        overflow: 'hidden',
        position: 'fixed',
        height: '100%',
        width: '100%',
        alignItems:'center',
        flexDirection: 'column',
        display:'flex',
        zIndex: 301,
        backgroundColor:'rgba(0, 0, 0, 0.5)',
    }
    const {width,height} = props;
    const {dragContorlClassName} = props;
    const [dragStartPosition,setDragStartPosition] = useState<ControlPosition | null>();
    useEffect(()=>{
        /*初始容器(可拖拽组件对于的dom元素)的高度 */ 
        if(height > CONTAINER_MIN_HEIGHT){
            setContainerHeight(height);
        }
    },[height]);
    const onDragStart = () => {
		dragStartPosition !== null && setDragStartPosition(null);
	}

	const onDragStop = (e:any, draggableData:any) => {
		calculatePositionAlwaysShowInView(e, draggableData,(x,y) =>{
            setDragStartPosition({x,y});
        });
	};
    let popoverRef = createRef<HTMLDivElement>();
    const {styles,cx} = useStyle();
    return (
        <>
            {/**弹出窗口的蒙层 */}
            <div style={popContainer}>
                {/** handler属性指定拖拽移动生效的样式类,因弹出窗口还可弹出窗口,其应该具有唯一性,
                 * 这个设计旨把拖拽移动和拖拽改变大小抽象出来,通常拖拽部分由调用方指定,所以这个样式
                 * 类名是一个组件属性由外部传入 */}  
                <Drag 
                    handle={dragContorlClassName}
                    defaultClassName="js-drag-wrapped"
                    position={dragStartPosition as Point}
                    onStart={onDragStart}
                    onStop={onDragStop}
                    >
                    {/** 下面的div作为拖拽区域的外部容器 */}             
                    <div style={{width:width}}>
                        <Resizable 
                            style={{}} 
                            onResize={onResize}
                            onResizeStop={onResizeStop}
                            minWidth={width}
                            defaultSize={{width: width,height:containerHeight}}
                            className={cx("js-resize-container popover-shadow",styles.resizeContainerShadow)}
                            >
                            {/** 下面的div用作Window的可见样式,例如圆角 
                             * 拖拽区域使用tansform圆角不起作用,背景色要透明
                             * 大小可变区域tansform圆角不起作用,背景色要透明
                             * 在可变区域增加div,设置圆角样式,child有颜色,需要设置overflow:hidden
                             */} 
                            <div className={cx(styles.winPopover,styles.clearFix)}
                                ref={popoverRef}
                                onClick={e => {
                                    e.stopPropagation();
                                }}
                                onDoubleClick={e => {
                                    e.stopPropagation();
                                }}
                            >
                                {/** 承载外部设计组件
                                 * 该实例只是展示了拖拽改变位置和大小,实际对外接口并不完善,需要根据自己的需求进行扩展
                                 * 原则:外部组件不能直接改变一个组件的状态,所以组件内公开能做什么事,由外部组件来通知,
                                 * 内部实际做具体的动作。
                                 */}
                                {children}
                            </div>
                        </Resizable>
                    </div>
                </Drag>
            </div>
        </>
    );
}

export default DragableWindow;

代码重点,请参照代码中的注释,这样描述更适合程序员来阅读。

这里强调的一下,标签布局的层次,否则读起来感觉一头雾水。如下图


布局容器层次

组件引用样例

模拟了一个参照弹出窗口样例(ReferWindow),见前面的视频。样例使用的是antd组件进行布局演示

import React,{Component} from 'react'
import DragableWindow from './DragableWindow'
import {Button} from 'antd'
import {Flex,Layout} from 'antd'
import {CloseOutlined,WindowsOutlined} from '@ant-design/icons'
import {getUniqueString} from './utils'
const { Header, Sider,Footer } = Layout;

interface ReferWindowProps {
    refType:string;
    container:Element|DocumentFragment;
    title?:string;
    headIcon?:React.ReactNode;
    headContents?:[React.ReactNode];
    headComps?:[];
    visible?:boolean;
    children?:any;
    closeListener?:(v:boolean) => void;
}
interface ReferWindowInnerProps {
    visible?:boolean;
    resizeHeight?:number;
    max:boolean;
    activeKey:number;
    isExpandLeftArea:boolean;
    isDragging:boolean;
}

class ReferWindow extends Component<ReferWindowProps,ReferWindowInnerProps>{
    constructor(props:ReferWindowProps){
        super(props);
        this.state = {
            visible:props.visible === undefined?true:props.visible,
            max: false,
            activeKey:0,
            isExpandLeftArea:false,
            isDragging:false
        };
        this.closeWindow.bind(this);
    }

    closeWindow = ()=>{
        const {closeListener} = this.props;
        closeListener&&closeListener(false);
    }
  
    render(): React.ReactNode {
        const {title,headIcon} = this.props;
        let winIcon = headIcon?headIcon:<WindowsOutlined/>;
        let {visible} = this.props;
        let uniqueString = getUniqueString();
        return (
           visible&&(
                <DragableWindow height={360} width={600} dragContorlClassName={`.drag-header-${uniqueString}`}>
                    <Layout style={{height:'100%'}}>
                        <Header className={`drag-header-${uniqueString}`} style={{ cursor:'move', display: 'flex', alignItems: 'center',backgroundColor:'#f1f1f1', height:'50px', lineHeight:'50px',padding:'0px 15px' }}>
                            <Flex justify="space-between" align={'center'} style={{width:'100%',height:'100%'}}  vertical={false}>
                                <Flex>{winIcon}<span style={{padding:'5px'}}>{title??title}</span></Flex>
                                <Flex align={'flex-end'}><CloseOutlined onClick={this.closeWindow} style={{cursor:'pointer'}}/></Flex>
                            </Flex>
                        </Header>
                        <Layout style={{}}>
                            <Sider width={200}>
                            
                            </Sider>
                        </Layout>
                        <Footer style={{padding:'10px 10px'}}>
                            <Flex justify={'flex-end'}>
                                <span><Button type='primary'>确定</Button></span>
                                <span style={{padding:'0px 5px'}}><Button  onClick={this.closeWindow}>取消</Button></span>
                            </Flex>
                        </Footer>
                    </Layout>
                </DragableWindow>
            ))
    };
}

export default ReferWindow;

参照

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import {Input} from 'antd';
import { BarsOutlined } from '@ant-design/icons';
import ReferWindow from './referWindow'

interface BaseReferProps {
    refType:string;
    container:Element|DocumentFragment;
    title?:string;
}
interface BaseReferInnerProps {
    popoverPanelVisible: boolean;
}
class BaseRefer extends Component<BaseReferProps,BaseReferInnerProps> {
    constructor(props:BaseReferProps){
        super(props);
        this.state = {
            popoverPanelVisible:false,
        };
        this.showPopoverPanel.bind(this)
        this.onReferWindowCloseClick.bind(this);
    }
    showPopoverPanel = () => {
        this.setState({popoverPanelVisible:true});
    }

    onReferWindowCloseClick = () =>{
        this.setState({popoverPanelVisible:false});
    }

    render(): React.ReactNode {
        const {container,refType,title} = this.props;
        let { popoverPanelVisible } = this.state;
        return <>
            <Input addonAfter={<BarsOutlined style={{cursor:'pointer'}} onClick={this.showPopoverPanel.bind(this)}/>}></Input>
            {ReactDOM.createPortal(
                <ReferWindow 
                visible={popoverPanelVisible} 
                closeListener={this.onReferWindowCloseClick}
                title={title} 
                refType={refType} 
                container={container}>
                </ReferWindow>,
            container
            )}  
        </>
    }
}

export default BaseRefer;

参照使用

import React from 'react';
import BaseRefer from './components/container/Refer/baseRefer'
import {Row,Col} from 'antd'
import './App.css';
const App : React.FC = () =>{
  return (
    
      <Row>
        <Col span={6}><BaseRefer refType='treeGrid' title='门店商品' container={document.body}></BaseRefer></Col>
        <Col span={18}></Col>
      </Row>
    
  )
}

export default App;

效果图

学生就可以看懂的bootstrap基础实战系列,pre标签里是笔记总结,动手实际操作一下会加强理解。有疑问留言交流哦。

bootstrap是进阶html5很基础常用的前端框架,可以做自适应漂亮的界面,再坚持一下,前端知识的大门就会打开了。

本节内容有动态显示效果,建议实际操作一下。