1

这三个小的组件是笔者对常见的一些操作的React封装,性能不一定优化,只是为了方便使用,随手写的,不过如果有什么功能建议欢迎开Issue
Github系列Repos

ScalableComponent

借鉴了pageResponse这个移动端响应式插件

核心思想就是基于CSS3 Transform中的scale变换,根据屏幕尺寸与视觉设计稿的尺寸对比进行自动释放,以求达到较好的浏览效果,适合于懒人快速适配。

Usage

设置视口

<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">

源代码

/**
 * Created by apple on 16/6/30.
 */
import React, {Component, PropTypes} from "react";

export default class ScalableComponent extends Component {

    //设置属性值
    static propTypes = {
        mode: PropTypes.oneOf(['auto', 'contain', 'cover']), //选择的模式
        width: PropTypes.number, //视觉稿宽度
        height: PropTypes.number, //视觉稿高度
        origin: PropTypes.string,//缩放中心点
        wrapperBackgroundColor: PropTypes.string//背景颜色
    };

    /**
     * @function 构造函数
     * @param props
     */
    constructor(props) {

        super(props);

        let userAgent = navigator.userAgent;

        //判断是否为指定设备
        this.wp = userAgent.match(/Windows Phone ([\d.]+)/); //判断是否为WindowsPhone
        this.android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/); //判断是否为Android

        //获取设备的宽高比
        this.state = {
            deviceWidth: document.documentElement.clientWidth,
            deviceHeight: document.documentElement.clientHeight
        };


        //根据模式判断页面缩放比例
        this.mode = this.props.mode || "auto";

        //缩放中心
        this.origin = this.props.origin || "left top 0";

        //传入的视觉稿
        this.visualWidth = this.props.width || 320;

        this.visualHeight = this.props.height || 504;

        this.wrapperBackgroundColor = this.props.wrapperBackgroundColor || "black";

        this._calcScaleRatio = this._calcScaleRatio.bind(this);

        this._updateDimensions = this._updateDimensions.bind(this);

    }

    /**
     * @function 为了避免重绘,在ComponentWillMount之前
     */

    componentDidMount() {
        //监听页面尺寸变化
        window.addEventListener("resize", this._updateDimensions);
    }

    componentWillUnmount() {
        //移除页面尺寸变化监听
        window.removeEventListener("resize", this._updateDimensions);
    }

    /**
     * @function 更新屏幕尺寸
     * @private
     */
    _updateDimensions() {
        this.setState({
            deviceWidth: document.documentElement.clientWidth,
            deviceHeight: document.documentElement.clientHeight
        });
    }

    /**
     * @function 计算缩放参数
     * @private
     */
    _calcScaleRatio() {

        //默认缩放比例为1
        let scaleRatio = 1;

        let deviceAspectRatio = this.state.deviceWidth / this.state.deviceHeight;

        //计算传入的视觉稿的比
        let visualAspectRatio = this.visualWidth / this.visualHeight;

        //计算缩放比
        if (this.mode === "contain") {
            //如果是包含模式,根据宽高中较大值进行缩放
            scaleRatio = deviceAspectRatio > visualAspectRatio ? this.state.deviceHeight / this.visualHeight : this.state.deviceWidth / this.visualWidth;


        } else if (this.mode === "cover") {

            scaleRatio = deviceAspectRatio < visualAspectRatio ? this.state.deviceHeight / this.visualHeight : this.state.deviceWidth / this.visualWidth;

        } else {

            scaleRatio = this.state.deviceWidth / this.visualWidth;

        }

        return scaleRatio;

    }

    /**
     * @function 默认的渲染函数
     * @returns {XML}
     */
    render() {

        const scaleRatio = this._calcScaleRatio();

        //设置包裹层样式
        let wrapperStyle = {
            position: "relative",
            width: "100%",
            height: "100%",
            backgroundColor: this.wrapperBackgroundColor,
            overflow: "hidden"
        };

        //设置内部元素的缩放属性
        let scaleStyle = {
            width: this.visualWidth,
            height: this.visualHeight,
            WebkitTransformOrigin: this.origin,
            transformOrigin: this.origin,
            WebkitTransform: `scale(${scaleRatio})`,
            transform: `scale(${scaleRatio})`
        };

        // 兼容android 2.3.5系统下body高度不自动刷新的bug
        if (this.mode === "auto" && this.android) {
            document.body.style.height = this.visualHeight * scaleRatio + "px";

        } else if (this.mode === "contain" || this.mode === "cover") {
            //如果是contain模式
            //设置为绝对定位
            scaleStyle.position = "absolute";

            scaleStyle.left = (this.state.deviceWidth - this.visualWidth) / 2 + "px";

            scaleStyle.top = (this.state.deviceHeight - this.visualHeight) / 2 + "px";

            scaleStyle.WebkitTransformOrigin = "center center 0";

            scaleStyle.transformOrigin = "center center 0";

            //阻止默认滑屏事件
            if (this.wp) {
                document.body.style.msTouchAction = "none";
            } else {
                document.ontouchmove = function (e) {
                    e.preventDefault()
                }
            }
        }

        return (<section style={wrapperStyle}>
            <div style={scaleStyle}>
                {/*直接将子元素放置在这里*/}
                {this.props.children}
            </div>
        </section>)
    }
}

Mode

Contain

Contain模式即保证页面完全包含在浏览器窗口中,在保证页面的宽高比情况下调整页面的宽度或者高度中的较大者,使得页面水平垂直居中,左右或上下可能出现空白。

Cover

Cover模式即使页面完全覆盖浏览器窗口,保持页面的宽高比,调整页面的宽度或高度(较小者),页面水平垂直居中,超出浏览器窗口左右或上下的内容会被隐藏

Auto

保持页面的宽高比,调整页面的宽度,使页面宽度完全包含在浏览器窗口中

Demo:Swiper封装

笔者一开始只是想对于Swiper.js做简单的封装,但是后来发现好像Swiper.js没有官方的isomorphic版本,最终为了方便还是选择封装一个同时加载外部脚本与样式的小组件。

ReactExternalLoader:外部脚本/组件加载封装组件

Usage

import React from "react";
import {render} from "react-dom";
import {ReactExternalLoader} from "../external_loader";

render(<ReactExternalLoader
    srcArray={[
        "https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.3.1/css/swiper.min.css",
        "https://cdnjs.cloudflare.com/ajax/libs/Swiper/3.3.1/js/swiper.min.js"
    ]}

    onLoad={()=>{alert("Loaded!");}}

>
    <div style={{color:"white"}}>
        <h1 style={{position:"absolute"}}>HI</h1>
        <p style={{position:"absolute",top:"50px"}}>This is Demo For Scalable</p>
        <img height="504px" width="320px" src="http://img5.cache.netease.com/photo/0031/2014-09-20/A6K9J0G94UUJ0031.jpg"
             alt=""/>
    </div>
</ReactExternalLoader>, document.getElementById('root'));

源代码

/**
 * Created by apple on 16/7/1.
 */
import React, {Component, PropTypes} from "react";

//加载ES6 Promise Polyfill
require('es6-promise').polyfill();

//判断某个脚本是否已经被加载
const promises = {};

/**
 * @function 用于加载Script脚本
 * @param src
 * @returns {*}
 */
function loadScript(src) {

    //判断该脚本是否被加载过
    if (promises[src]) {
        return promises[src]
    }

    //构造一个Promise对象
    let promise = promises[src] = new Promise(resolve => {

        //创建一个script元素
        var el = document.createElement('script');

        var loaded = false;

        //设置加载完成的回调事件
        el.onload = el.onreadystatechange = () => {

            //防止重复加载
            if ((el.readyState && el.readyState !== 'complete' && el.readyState !== 'loaded') || loaded) {
                return false;
            }

            //加载完成后将该脚本的onload设置为null
            el.onload = el.onreadystatechange = null;

            loaded = true;

            resolve();
        };

        el.async = true;

        el.src = src;

        let head = document.getElementsByTagName('head')[0];

        head.insertBefore(el, head.firstChild);

    });

    return promise;

}

/**
 * @function 用于加载CSS文件
 * @param src
 * @returns {*}
 */
function loadCSS(src) {

    //判断该脚本是否被加载过
    if (promises[src]) {
        return promises[src]
    }

    //构造一个Promise对象
    let promise = promises[src] = new Promise(resolve => {

        //创建一个script元素
        var el = document.createElement('link');

        el.rel = "stylesheet";

        el.href = src;

        el.media = "only x";

        var loaded = false;

        //设置加载完成的回调事件
        el.onload = el.onreadystatechange = () => {

            //防止重复加载
            if ((el.readyState && el.readyState !== 'complete' && el.readyState !== 'loaded') || loaded) {
                return false;
            }

            //加载完成后将该脚本的onload设置为null
            el.onload = el.onreadystatechange = null;

            loaded = true;

            el.media = "all";

            resolve();
        };

        //获取文档头元素
        let head = document.getElementsByTagName('head')[0];

        //插入刚才创建好的元素
        head.insertBefore(el, head.firstChild);

    });

    return promise;

}

export class ReactExternalLoader extends Component {

    //设置默认的Props
    static defaultProps = {

        //需要加载的外部资源地址
        srcArray: ['javascript:void(0)'],

        //正在加载的指示
        loadingIndicator: (<div>
            Loading
        </div>),

        //加载完成回调
        onLoad: () => {
        }
    };

    //设置需要载入的Props
    static propTypes = {
        srcArray: React.PropTypes.array,
        loadingIndicator: React.PropTypes.object,
        onLoad: React.PropTypes.func
    };

    //全局状态
    state = {
        done: false
    };

    /**
     * @function 组件加载完毕之前的回调
     */
    componentWillMount() {

        Promise.all(
            this.props.srcArray.map((src)=> {

                //判断是否为JS
                if (typeof src === "string" && src.indexOf(".js") > -1) {
                    return loadScript(src);
                } else {
                    return loadCSS(src);
                }

            })
        ).then(
            ()=> {

                //设置加载完成
                this.setState({
                    done: true
                });

                //调用外部的完成回调
                this.props.onLoad();
            }
        );

    }

    /**
     * @function 渲染方法
     * @returns {*}
     */
    render() {

        //如果加载完成
        if (this.state.done) {

            //返回实际的子组件
            return this.props.children
        } else {

            //返回加载指示
            return this.props.loadingIndicator;

        }
    }


}

Swiper封装与使用

Usage

/**
 * Created by apple on 16/7/4.
 */
import React from "react";
import {render} from "react-dom";
import ScalableComponent from "../scalable";
import {SwiperContainer, SwiperSlide} from "../../scroll/slider/swiper";


render(
    <ScalableComponent mode="contain" wrapperBackgroundColor="rgb(117,155,156)">

        <SwiperContainer options={{
        pagination: '.swiper-pagination',
        paginationClickable: true,
        direction: 'vertical'
    }}>

            <SwiperSlide>
                <div style={{color:"white"}}>
                    <h1 style={{position:"absolute"}}>HI Slide1</h1>
                    <p style={{position:"absolute",top:"50px"}}>This is Demo For Scalable</p>
                    <img height="504px" width="320px"
                         src="http://img5.cache.netease.com/photo/0031/2014-09-20/A6K9J0G94UUJ0031.jpg"
                         alt=""/>
                </div>
            </SwiperSlide>

            <SwiperSlide>
                <div style={{color:"white"}}>
                    <h1 style={{position:"absolute"}}>HI Slide2</h1>
                    <p style={{position:"absolute",top:"50px"}}>This is Demo For Scalable</p>
                    <img height="504px" width="320px"
                         src="http://img5.cache.netease.com/photo/0031/2014-09-20/A6K9J0G94UUJ0031.jpg"
                         alt=""/>
                </div>
            </SwiperSlide>

        </SwiperContainer>

    </ScalableComponent>, document.getElementById('root'));

最终效果:


王下邀月熊_Chevalier
22.5k 声望8.5k 粉丝

爱代码 爱生活 希望成为全栈整合师