这三个小的组件是笔者对常见的一些操作的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'));
最终效果:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。