├── .babelrc ├── README.md ├── lib └── index.js ├── package.json └── src ├── index.js └── shot.gif /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-mult-transition-image-view 2 | 3 | a graceful react component for image display. 4 | 5 | ---- 6 | 一个react图片显示组件 [segmentfault 使用说明](https://segmentfault.com/a/1190000011762388) 7 | 8 | | 参数 | 描述 | | 9 | | ------------- |:-------------:| :----- | 10 | | width | 宽度 ( 可选 ) | 会被转换成 style 的 width height | 11 | | height | 高度 ( 可选 ) | | 12 | | animate | 动画 ( 可选 ) | 默认: 'none' , 设置 'fade' 会加入 动画 class img-animate 来实现过渡动画) | 13 | | mode | 模式 ( 可选 ) | 默认: style | 14 | | img | 模式 ( 必选 ) | 可以是 url:string , 或者 是 [] 图片列表 | 15 | 16 | **图片过渡** 17 | 通过传入 img: [ img_sml , img_big ] ,来实现 图片从低质量 过渡到高质量来 防止大图 加载慢引起的空白。 18 | 19 | **注意** 20 | 样式可以参考下面的 less 代码 21 | 22 | 23 | ![加载截图](https://raw.githubusercontent.com/qilei0529/react-mult-transition-image-view/edb9aeb4520bac40106cd4cdeab2b72c727181d2/src/shot.gif) 24 | 25 | 26 | ### UPDATE 27 | 1.0.5 fix component receive props can not update 28 | 29 | 30 | ---- 31 | ### Use 32 | 33 | npm install react-mult-transition-image-view 34 | 35 | ### Sample code 36 | 37 | ```` react jsx 38 | 39 | import ImageBoxView from 'react-mult-transition-image-view' 40 | 41 | 51 | ```` 52 | 53 | 54 | 55 | ### Style 56 | 57 | ```` less 58 | .c-img-box{ 59 | display:inline-block; 60 | width: 320px; 61 | height: 200px; 62 | background: #f7f6f5; 63 | position: relative; 64 | .img-hold{ 65 | overflow: hidden; 66 | background-size: cover; 67 | background-repeat: no-repeat; 68 | background-position: center; 69 | img{ 70 | width:100%; 71 | height:100%; 72 | } 73 | &.img-animate{ 74 | transition: opacity 0.5s; 75 | } 76 | } 77 | 78 | .img-cover{ 79 | background: url('https://d.2dfire.com/om/images/menulist/7deb58da.default.png') no-repeat center/300px; 80 | background-color:#f0f0f0; 81 | } 82 | 83 | .img-cover, 84 | .img-hold, 85 | .img-hide{ 86 | position: absolute; 87 | width: 100%; 88 | height: 100%; 89 | top:0; 90 | left:0; 91 | } 92 | 93 | .img-hide{ 94 | opacity: 0; 95 | } 96 | 97 | } 98 | ```` 99 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _react = require('react'); 12 | 13 | var _classnames3 = require('classnames'); 14 | 15 | var _classnames4 = _interopRequireDefault(_classnames3); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 20 | 21 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 22 | 23 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 24 | 25 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 26 | 27 | // 计算 图片的 数量 28 | var img_count = 0; 29 | 30 | var ImageBoxView = function (_Component) { 31 | _inherits(ImageBoxView, _Component); 32 | 33 | function ImageBoxView(props) { 34 | _classCallCheck(this, ImageBoxView); 35 | 36 | var _this = _possibleConstructorReturn(this, (ImageBoxView.__proto__ || Object.getPrototypeOf(ImageBoxView)).call(this, props)); 37 | 38 | _this.init = _this.init.bind(_this); 39 | _this.onFetchImg = _this.onFetchImg.bind(_this); 40 | _this.show = _this.show.bind(_this); 41 | 42 | _this._name = img_count++; 43 | 44 | var state = _this.init(props); 45 | _this.state = state; 46 | return _this; 47 | } 48 | 49 | _createClass(ImageBoxView, [{ 50 | key: 'init', 51 | value: function init(props) { 52 | var img = props.img, 53 | _props$animate = props.animate, 54 | animate = _props$animate === undefined ? '' : _props$animate, 55 | _props$mode = props.mode, 56 | mode = _props$mode === undefined ? '' : _props$mode, 57 | _props$delay = props.delay, 58 | delay = _props$delay === undefined ? 0 : _props$delay, 59 | _props$wait = props.wait, 60 | wait = _props$wait === undefined ? 0 : _props$wait, 61 | _props$width = props.width, 62 | width = _props$width === undefined ? 0 : _props$width, 63 | _props$height = props.height, 64 | height = _props$height === undefined ? 0 : _props$height; 65 | 66 | var img_list = []; 67 | 68 | if (typeof img == 'string' && img.length > 0) { 69 | 70 | var img_item = { 71 | url: img, 72 | status: 0, // 0 no 1: ready 73 | fade: 0 }; 74 | 75 | img_list.push(img_item); 76 | } else if ((typeof img === 'undefined' ? 'undefined' : _typeof(img)) == 'object' && img.length > 0) { 77 | 78 | img.map(function (item) { 79 | var img_item = { 80 | url: item, 81 | status: 0, 82 | fade: 0 83 | }; 84 | img_list.push(img_item); 85 | }); 86 | } 87 | 88 | var state = { 89 | show: false, 90 | img_list: img_list, 91 | animate: animate == 'fade', 92 | style_mode: mode == 'style', 93 | delay: parseInt(delay), 94 | wait: parseInt(wait), 95 | 96 | size: { 97 | width: parseInt(width), 98 | height: parseInt(height) 99 | } 100 | }; 101 | 102 | return state; 103 | } 104 | }, { 105 | key: 'show', 106 | value: function show(flag) { 107 | this.setState({ 108 | show: flag ? true : false 109 | }); 110 | } 111 | }, { 112 | key: 'componentDidMount', 113 | value: function componentDidMount() { 114 | var _this2 = this; 115 | 116 | var delay = this.state.delay; 117 | 118 | 119 | if (delay) { 120 | this._timer = setTimeout(function () { 121 | _this2.show(1); 122 | }, delay); 123 | } else { 124 | this.show(1); 125 | } 126 | } 127 | }, { 128 | key: 'componentWillUnmount', 129 | value: function componentWillUnmount() { 130 | this._timer && clearTimeout(this._timer); 131 | } 132 | }, { 133 | key: 'componentWillReceiveProps', 134 | value: function componentWillReceiveProps(nextProps) { 135 | var img = nextProps.img; 136 | 137 | if (img != this.state.img_data) { 138 | var state = this.init(nextProps); 139 | state.show = true; 140 | this.setState(state); 141 | } 142 | } 143 | }, { 144 | key: 'onFetchImg', 145 | value: function onFetchImg(index) { 146 | var _this3 = this; 147 | 148 | var _state = this.state, 149 | _state$img_list = _state.img_list, 150 | img_list = _state$img_list === undefined ? [] : _state$img_list, 151 | _state$animate = _state.animate, 152 | animate = _state$animate === undefined ? false : _state$animate, 153 | _state$style_mode = _state.style_mode, 154 | style_mode = _state$style_mode === undefined ? false : _state$style_mode; 155 | 156 | 157 | var img_item = img_list[index]; 158 | if (img_item == undefined) { 159 | return; 160 | } 161 | 162 | img_item.status = 1; 163 | 164 | if (style_mode) { 165 | img_item.style = { 166 | backgroundImage: 'url(' + img_item.url + ')' 167 | }; 168 | } 169 | 170 | if (animate && index > 0) { 171 | 172 | var img_ref = 'img_' + index; 173 | var img_dom = this.refs[img_ref]; 174 | 175 | var img_name = 'img_' + this._name + '_' + index; 176 | 177 | if (img_dom) { 178 | img_dom.addEventListener("webkitTransitionEnd", function () { 179 | img_item.fade = 1; 180 | _this3.setState({ img_list: img_list }); 181 | }); 182 | } 183 | } else { 184 | img_item.fade = 1; 185 | } 186 | this.setState({ img_list: img_list }); 187 | } 188 | }, { 189 | key: 'onLoadImg', 190 | value: function onLoadImg(index) { 191 | var _this4 = this; 192 | 193 | var wait = this.state.wait; 194 | 195 | 196 | if (wait && index > 0) { 197 | // let delay = parseInt(Math.random() * 2000) 198 | this._timer = setTimeout(function () { 199 | _this4.onFetchImg(index); 200 | }, wait); 201 | } else { 202 | this.onFetchImg(index); 203 | } 204 | } 205 | }, { 206 | key: 'render', 207 | value: function render() { 208 | var _this5 = this; 209 | 210 | var _state2 = this.state, 211 | _state2$img_list = _state2.img_list, 212 | img_list = _state2$img_list === undefined ? [] : _state2$img_list, 213 | _state2$animate = _state2.animate, 214 | animate = _state2$animate === undefined ? false : _state2$animate, 215 | show = _state2.show, 216 | _state2$size = _state2.size, 217 | size = _state2$size === undefined ? {} : _state2$size; 218 | 219 | 220 | var is_show_cover = img_list.length == 0; 221 | 222 | var first = img_list[0]; 223 | if (first) { 224 | is_show_cover = first.fade == 0; 225 | } 226 | 227 | var img_name = 'img_' + this._name; 228 | 229 | var className = this.props.className; 230 | 231 | 232 | var container_cls = (0, _classnames4.default)(_defineProperty({ 233 | 'c-img-box': true 234 | }, className, className)); 235 | 236 | var container_style = {}; 237 | 238 | if (size.width) { 239 | container_style.width = size.width + 'px'; 240 | } 241 | if (size.height) { 242 | container_style.height = size.height + 'px'; 243 | } 244 | 245 | return React.createElement( 246 | 'div', 247 | { className: container_cls, style: container_style, ref: 'container' }, 248 | is_show_cover && React.createElement('div', { className: 'img-cover' }), 249 | show && img_list.map(function (item, index) { 250 | var _item$url = item.url, 251 | url = _item$url === undefined ? '' : _item$url, 252 | _item$style = item.style, 253 | style = _item$style === undefined ? null : _item$style; 254 | 255 | if (index > 0) { 256 | var last = img_list[index - 1]; 257 | // 如果 前面的图 还没加载好 则 不显示 258 | if (last && last.status == 0) { 259 | return null; 260 | } 261 | } 262 | 263 | if (index < img_list.length) { 264 | var next = img_list[index + 1]; 265 | if (next && next.fade == 1) { 266 | return null; 267 | } 268 | } 269 | 270 | var ref = 'img_' + index; 271 | 272 | var img_cls = (0, _classnames4.default)(_defineProperty({ 273 | 'img-hold': item.status, 274 | 'img-hide': !item.status, 275 | 'img-animate': item.status && animate 276 | }, ref, true)); 277 | 278 | var is_show_img_dom = !style; 279 | return React.createElement( 280 | 'div', 281 | { ref: ref, className: img_cls, style: style, onLoad: _this5.onLoadImg.bind(_this5, index) }, 282 | is_show_img_dom && React.createElement('img', { src: url }) 283 | ); 284 | }) 285 | ); 286 | } 287 | }]); 288 | 289 | return ImageBoxView; 290 | }(_react.Component); 291 | 292 | exports.default = ImageBoxView; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mult-transition-image-view", 3 | "version": "1.0.5", 4 | "description": "a graceful react component for image display.", 5 | "main": "lib/index.js", 6 | "keywords": [ 7 | "react" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "babel": "babel src/ -d lib/" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/qilei0529/react-mult-transition-image-view.git" 16 | }, 17 | "files": [ 18 | "lib/" 19 | ], 20 | "author": "qilei", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "babel-preset-env": "^1.6.0", 24 | "babel-preset-react": "^6.24.1" 25 | }, 26 | "dependencies": { 27 | "classnames": "^2.2.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { Component } from 'react' 3 | 4 | import classnames from 'classnames' 5 | 6 | // 计算 图片的 数量 7 | let img_count = 0 8 | 9 | class ImageBoxView extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | 14 | this.init = this.init.bind(this) 15 | this.onFetchImg = this.onFetchImg.bind(this) 16 | this.show = this.show.bind(this) 17 | 18 | this._name = img_count++ 19 | 20 | let state = this.init(props) 21 | this.state = state 22 | } 23 | 24 | init( props ) { 25 | 26 | let { img , animate = '' , mode = '' , delay = 0 , wait = 0 , width=0, height=0 } = props 27 | let img_list = [] 28 | 29 | if ( typeof img == 'string' && img.length > 0 ){ 30 | 31 | let img_item = { 32 | url: img, 33 | status: 0, // 0 no 1: ready 34 | fade: 0, // 0 1: fade ready 35 | } 36 | 37 | img_list.push( img_item ) 38 | 39 | }else if ( typeof img == 'object' && img.length > 0) { 40 | 41 | img.map( ( item ) => { 42 | let img_item = { 43 | url: item, 44 | status: 0, 45 | fade: 0 46 | } 47 | img_list.push( img_item ) 48 | }) 49 | 50 | } 51 | 52 | let state = { 53 | show: false, 54 | img_list: img_list, 55 | animate: animate == 'fade', 56 | style_mode: mode == 'style', 57 | delay: parseInt(delay), 58 | wait: parseInt(wait), 59 | 60 | size: { 61 | width: parseInt(width), 62 | height: parseInt(height) 63 | } 64 | }; 65 | 66 | return state 67 | } 68 | 69 | show( flag ) { 70 | this.setState({ 71 | show: flag ? true : false 72 | }) 73 | } 74 | 75 | componentDidMount() { 76 | let { delay } = this.state 77 | 78 | 79 | if ( delay ) { 80 | this._timer = setTimeout(() => { 81 | this.show(1) 82 | } , delay) 83 | }else{ 84 | this.show(1) 85 | } 86 | } 87 | 88 | componentWillUnmount() { 89 | this._timer && clearTimeout(this._timer) 90 | } 91 | 92 | componentWillReceiveProps(nextProps) { 93 | let { img } = nextProps 94 | if (img != this.state.img_data) { 95 | let state = this.init(nextProps) 96 | state.show = true 97 | this.setState(state) 98 | } 99 | } 100 | 101 | onFetchImg( index ) { 102 | 103 | let { img_list = [] , animate = false , style_mode = false } = this.state 104 | 105 | let img_item = img_list[index] 106 | if ( img_item == undefined ) { 107 | return 108 | } 109 | 110 | img_item.status = 1 111 | 112 | if ( style_mode ) { 113 | img_item.style = { 114 | backgroundImage:`url(${img_item.url})`, 115 | } 116 | } 117 | 118 | if( animate && index > 0 ) { 119 | 120 | let img_ref = 'img_' + index 121 | let img_dom = this.refs[img_ref] 122 | 123 | let img_name = 'img_' + this._name + '_' + index 124 | 125 | if( img_dom ) { 126 | img_dom.addEventListener("webkitTransitionEnd", () => { 127 | img_item.fade = 1 128 | this.setState({ img_list }) 129 | }) 130 | } 131 | 132 | }else{ 133 | img_item.fade = 1 134 | } 135 | this.setState({ img_list }) 136 | } 137 | 138 | onLoadImg( index ) { 139 | 140 | let { wait } = this.state 141 | 142 | if (wait && index > 0 ) { 143 | // let delay = parseInt(Math.random() * 2000) 144 | this._timer = setTimeout(()=> { 145 | this.onFetchImg(index) 146 | } ,wait) 147 | }else{ 148 | this.onFetchImg(index) 149 | } 150 | 151 | } 152 | 153 | render() { 154 | 155 | let { img_list = [] , animate = false , show , size = {} } = this.state 156 | 157 | let is_show_cover = img_list.length == 0 158 | 159 | let first = img_list[0] 160 | if( first ) { 161 | is_show_cover = first.fade == 0 162 | } 163 | 164 | let img_name = 'img_' + this._name 165 | 166 | 167 | let { className } = this.props 168 | 169 | let container_cls = classnames({ 170 | 'c-img-box' : true, 171 | [className] : className 172 | }) 173 | 174 | let container_style = {} 175 | 176 | if( size.width ) { 177 | container_style.width = size.width + 'px'; 178 | } 179 | if( size.height ) { 180 | container_style.height = size.height + 'px'; 181 | } 182 | 183 | return ( 184 |
185 | { 186 | is_show_cover && (
) 187 | } 188 | { 189 | show && img_list.map(( item , index ) => { 190 | 191 | let { url = '' , style = null } = item 192 | if( index > 0) { 193 | let last = img_list[index-1] 194 | // 如果 前面的图 还没加载好 则 不显示 195 | if ( last && last.status == 0 ) { 196 | return null 197 | } 198 | } 199 | 200 | if ( index < img_list.length ) { 201 | let next = img_list[index + 1] 202 | if( next && next.fade == 1 ) { 203 | return null 204 | } 205 | } 206 | 207 | let ref = 'img_' + index 208 | 209 | let img_cls = classnames({ 210 | 'img-hold' : item.status, 211 | 'img-hide' : !item.status, 212 | 'img-animate' : item.status && animate, 213 | [ref] : true 214 | }) 215 | 216 | let is_show_img_dom = !style 217 | return ( 218 |
219 | { 220 | is_show_img_dom && 221 | } 222 |
223 | ) 224 | }) 225 | } 226 |
227 | ) 228 | } 229 | } 230 | 231 | 232 | export default ImageBoxView -------------------------------------------------------------------------------- /src/shot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qilei0529/react-mult-transition-image-view/b6307ae5e362b6aa5ec3dac603e9b15b0b1a2105/src/shot.gif --------------------------------------------------------------------------------