Repository: guonanci/react-images-viewer Branch: master Commit: 67a0da9b536b Files: 58 Total size: 117.2 KB Directory structure: gitextract_t5wlb761/ ├── .babelrc ├── .coveralls.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── CONTRIBUTING.md │ ├── HISTORY.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── README_CN.md ├── __mocks__/ │ └── ImgsViewer.js ├── __test__/ │ ├── Gallery.test.js │ ├── jestsetup.js │ ├── rafShim.js │ └── utils.test.js ├── ci.sh ├── examples/ │ └── src/ │ ├── .gitignore │ ├── .npmignore │ ├── app.js │ ├── app_CN.js │ ├── components/ │ │ ├── DownloadButton/ │ │ │ ├── icon.js │ │ │ └── index.js │ │ ├── Gallery.js │ │ └── Spinner.js │ ├── example.less │ ├── index.html │ ├── index_CN.html │ └── standalone.html ├── index.js ├── jest.config.js ├── new-branch.sh ├── package-scripts.js ├── package.json ├── rollup.config.js ├── src/ │ ├── ImgsViewer.js │ ├── components/ │ │ ├── Arrow.js │ │ ├── Container.js │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── Icon.js │ │ ├── PaginatedThumbnails.js │ │ ├── Portal.js │ │ ├── Spinner.js │ │ ├── Thumbnail.js │ │ └── Thumbnails.js │ ├── icons/ │ │ ├── Close.js │ │ ├── arrow_left.js │ │ ├── arrow_right.js │ │ └── close.js │ ├── theme.js │ └── utils/ │ ├── constant.js │ └── util.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "ignore": ["node-modules"], "presets": ["env", "react"], "plugins": [ ["transform-object-rest-spread", { "useBuiltIns": true }], "transform-object-assign"], "env": { "test": { "presets": [ ["env"], "react" ], "plugins": ["istanbul"] } } } ================================================ FILE: .coveralls.yml ================================================ service_name: travis-ci repo_token: S5CSBnH2886RuV3s0ecSjaam2N4JOjsC7 # coverageReporter: { # dir: path.join(__dirname, 'coverage'), # reporters: [ # {type: 'html'}, # {type: 'lcov', subdir: 'lcov'} // lcov # ] # }, ================================================ FILE: .editorconfig ================================================ # This file is for unifying the coding style for different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: .eslintignore ================================================ lib/* dist/* coverage/* node_modules/* ================================================ FILE: .eslintrc.js ================================================ // https://eslint.org/docs/user-guide/configuring module.exports = { root: true, parser: 'babel-eslint', parserOptions: { ecmaFeatures: { jsx: true, }, }, parserOptions: { sourceType: 'module' }, env: { node: true, browser: true, commonjs: true, es6: true, jest: true, }, extends: [ 'eslint:recommended', 'plugin:react/recommended', ], // required to lint *.vue files plugins: [ 'react', ], // check if imports actually resolve settings: { react: { createClass: 'createReactClass', pragma: 'React', version: '16.4.1' }, }, rules: { 'no-unused-vars': 0, // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'no-console': process.env.NODE_ENV === 'production' ? 2 : 0, "no-unused-vars": [2, { "vars": "all", "args": "after-used" }], 'global-require': 0, } } ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing Thanks for your interest in react-images-viewer. All forms fo contribution ares welcome, from issue reports to PRs and documentation / write-ups. Before you open a PR: * If you're planning to add or change a major feature in a PR, please ensure the change is aligned with the project roadmap by opening an issue first, especially if you're going to spend a lot of time on it. * In development, run `yarn start` to build(+watch) the project source, and run the [development server](http://localhost:8000). * Please ensure all the examples work correctly after your change. If you're adding a major new use-case, add a new example demonstrating its use. * Please **DO NOT** commit the build files. Make sure **ONLY** your changes to `/src/` and `/examples/src` are included in your PR. * Be careful to follow the code style of the project. Run `yarn ru lint` after your changes and ensure you do not introduce any new errors or warnings. * All new features and changes need documentation. * Make sure yarn.lock is updated when you add or upgrade dependencies. ================================================ FILE: .github/HISTORY.md ================================================ # React-Images-Viewer ### v1.6.8 / 2022-01-03 - Update: fix issues: #28, #38 ### v1.3.0 / 2018-07-24 - Update: Make README.md and README_ZN.md desciption more specific - Fix: correct `package.json - main` field value --- ### v1.0.5 / 2018-07-24 - Basic, thumbnailed, themed usage all work well - Feature: you can use `up, right, bottom, left, space, esc` to navigate quickly - Component API is very brief and precise ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ **Steps to reproduce the behavior:** **Expected behavior:** **Actual behavior:** ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ **Description of changes:** **Related issues (if any):** **Checks:** - [ ] Please confirm `yarn run lint` ran successfully - [ ] Please confirm that only `/src` and `/examples/src` are committed - [ ] [if new feature] Please confim that docuemtation was added to the README.md ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Build lib dist examples/dist # Webstorm .idea/ # VSCode .vscode/ # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # node-waf configuration .lock-wscript # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Site build .publish # Dependency directories # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules/ jspm_packages/ # temp files .swp # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # next.js build output .next __test__/__snapshots__/ .js.snap # MacOS generated file .DS_Store ================================================ FILE: .npmignore ================================================ examples ================================================ FILE: .travis.yml ================================================ language: node_js sudo: false node_js: - stable cache: directories: - node_modules install: | npm install -g yarn npm --version yarn script: - yarn lint - yarn test - yarn coverage ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 yiminanci Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # react-images-viewer [![Build Status](https://travis-ci.org/guonanci/react-images-viewer.svg?branch=master)](https://travis-ci.org/guonanci/react-images-viewer) [![jest](https://jestjs.io/img/jest-badge.svg)](https://github.com/facebook/jest) A react library that view photos list easily, and a simple, responsive viewer component for displaying an array of images. [中文文档](./README_CN.md) ## Quick start ```bash # recommended yarn add react-images-viewer ``` or ```bash npm install react-images-viewer --save ``` ```jsx import React from "react"; import ImgsViewer from "react-images-viewer"; export default class Demo extends React.Component { render() { return ( ); } } ``` ## Demo & Example Live Demo: [guonanci.github.io/react-images-viewer](https://guonanci.github.io/react-images-viewer) ```bash yarn install yarn yarn start ``` Then open [`localhost:8000`](http://localhost:8000) in a browser. ### Using srcSet Example using srcSet: ```jsx ``` ```js const IMG_SET = [ { src: "http://example.com/img1.svg", caption: "A forest", // As an array srcSet: [ "http://example.com/img1_1024.jpg 1024w", "http://example.com/img1_800.jpg 800w", "http://example.com/img1_500.jpg 500w", "http://example.com/img1_320.jpg 320w", ], }, { src: "http://example.com/img2.svg", // As a string srcSet: "http://example.com/img2_1024.jpg 1024w, http://example.com/img2_800.jpg 800w, http://example.com/img2_500.jpg 500w, http://example.com/img2_320.jpg 320w", }, ]; ``` ## Options | Property | Type | Default | Description | | :------------------ | :--------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------ | | backdropCloseable | `boolean` | `false` | Allow users to exit the viewer by clicking the backdrop | | closeBtnTitle | `str` | `undefined` | Customize close esc title | | enableKeyboardInput | `boolean` | `true` | Supports keyboard input - space, esc, arrow left, arrow up, and arrow right, arrow down | | currImg | `number` | `0` | Required if you want to navigate the imgsViewer, The index of the image to display initially | | customControls | arr | `undefined` | An array of elements to display as custom controls on the top of viewer | | imgs | arr | `undefined` | Required. Array of image objects, See img opts table below | | imgCountSeparator | `str` | `' / '` | Customize separator in the image count | | isOpen | `boolean` | `false` | Required if you want to navigate the imgsViewer, Whether or not the viewer is displayed | | leftArrowTitle | `str` | `undefined` | Customize of left arrow title | | onClickPrev | `function` | `undefined` | Required if you want to navigate the imgsViewer, and fired on request of the previous image | | onClickNext | `function` | `undefined` | Required if you want to navigate the imgsViewer, and fired on request of the next image | | onClose | `function` | `undefined` | Required if you want to close the imgsViewer, and handle closing of the viewer | | onClickImg | `function` | `undefined` | Handle click on current image | | onClickThumbnail | `function` | `undefined` | Handle click on thumbnail | | preloadNextImg | `boolean` | `true` | Whether to preload the next available image | | rightArrowTitle | `str` | `undefined` | Customize right arrow title | | showCloseBtn | `boolean` | `true` | Optionally display a close 'X' button in top right corner | | showImgCount | `boolean` | `true` | Optionally display image index, e.g., "2 of 20" | | width | `number` | `1024` | Maximum width of the carousel; defaults to 1024px | | spinnerDisabled | `boolean` | `false` | Disable Spinner | | spinner | `function` | `DefaultSpinner` | Spinner component class | | spinnerColor | `str` | `'#fff'` | Color of spinner | | spinnerSize | `number` | `50` | Size of spinner | | preventAutoScroll | `boolean` | `true` | Determines whether auto-scrolling is prevented | ## Images Object | Property | Type | Default | Description | | :------- | :-------- | :---------- | :---------- | | src | `str` | `undefined` | Required | | srcSet | `string[]` | `undefined` | Optional | | caption | `str` | `undefined` | Optional | | alt | `str` | `undefined` | Optional | ================================================ FILE: README_CN.md ================================================ # react-images-viewer [![Build Status](https://travis-ci.org/guonanci/react-images-viewer.svg?branch=master)](https://travis-ci.org/guonanci/react-images-viewer) [![jest](https://jestjs.io/img/jest-badge.svg)](https://github.com/facebook/jest) 一个简单易用,响应式,放大并查看一组图片的 React 库。 [English document](./README.md) ## 快速入门 ```bash # 推荐 yarn add react-images-viewer ``` or ```bash npm install react-images-viewer --save ``` ```jsx import React from "react"; import ImgsViewer from "react-images-viewer"; export default class Demo extends React.Component { render() { return ( ); } } ``` ## 例子 线上案例: [guonanci.github.io/react-images-viewer](https://guonanci.github.io/react-images-viewer) ```bash yarn install yarn yarn start ``` 然后就可以在浏览器打开 [`localhost:8000`](http://localhost:8000) 窗口了。 ### srcSet 使用 例子: ```jsx ``` ```js const IMG_SET = [ { src: "http://example.com/img1.svg", caption: "A forest", // As an array srcSet: [ "http://example.com/img1_1024.jpg 1024w", "http://example.com/img1_800.jpg 800w", "http://example.com/img1_500.jpg 500w", "http://example.com/img1_320.jpg 320w", ], }, { src: "http://example.com/img2.svg", // As a string srcSet: "http://example.com/img2_1024.jpg 1024w, http://example.com/img2_800.jpg 800w, http://example.com/img2_500.jpg 500w, http://example.com/img2_320.jpg 320w", }, ]; ``` ## 选项 | Property | Type | Default | Description | | :------------------ | :--------- | :--------------- | :-------------------------------------------------------------------------------------------------- | | backdropCloseable | `布尔值` | `false` | 是否通过点击半透明幕布来退出浏览 | | closeBtnTitle | `字符串` | `undefined` | 关闭按钮的 title | | enableKeyboardInput | `布尔值` | `true` | 支持键盘输入 - 空格键,esc, 左箭头,上箭头,和右箭头,下箭头 | | currImg | `数值类型` | `0` | 必须项(如果需要导航的话),初始化图像的索引 | | customCtrls | `数组` | `undefined` | 图片查看器顶部的控件元素数组 | | imgs | `数组` | `undefined` | 必须项. 图片元素数组,图像选项见下表。 | | imgsSeparator | `字符串` | `' / '` | 图片计数分隔符 | | isOpen | `布尔值` | `false` | 必须项(如果需要导航的话),图片是否显示 | | leftArrowTitle | `字符串` | `undefined` | 左箭头的 title | | onClickPrev | `函数` | `undefined` | 必须项(如果需要导航的话),请求上一张时触发 | | onClickNext | `函数` | `undefined` | 必须项(如果需要导航的话),请求下一张时触发 | | onClose | `函数` | `undefined` | 关闭查看器的回调 | | onClickImg | `函数` | `undefined` | 点起当前图片的回调 | | onClickThumbnail | `函数` | `undefined` | 缩略点击的回调 | | preloadNextImg | `布尔值` | `true` | 是否预加载下一张图片 | | rightArrowTitle | `字符串` | `undefined` | 右箭头的 title | | showCloseBtn | `布尔值` | `true` | 右上角是否显示 X 按钮 | | showImgCount | `布尔值` | `true` | 是否显示图片索引 | | width | `数值类型` | `1024` | 轮播器的最大的宽度,默认值 1024px | | spinnerDisabled | `布尔值` | `false` | 禁用加载器 | | spinner | `函数` | `DefaultSpinner` | 加载器组件 | | spinnerColor | `字符串` | `'#fff'` | 加载器颜色 | | spinnerSize | `数值类型` | `50` | 加载器尺寸 | | preventAutoScroll | `布尔值` | `true` | 是否阻止自动滚动 | ## Images Object | Property | Type | Default | Description | | :------- | :--------------------- | :---------- | :---------- | | src | `字符串` | `undefined` | 必须项 | | srcSet | `字符串`数组或者字符串 | `undefined` | 可选项 | | caption | `字符串` | `undefined` | 可选项 | | alt | `字符串` | `undefined` | 可选项 | ================================================ FILE: __mocks__/ImgsViewer.js ================================================ ================================================ FILE: __test__/Gallery.test.js ================================================ import React from 'react' import ImgsViewer from '../src/ImgsViewer' import { mount } from 'enzyme' const props1 = { onClose: () => {}, imgs: [ { src: 'https://images.unsplash.com/photo-1526382551041-3c817fc3d478?dpr=2&auto=format&w=1024&h=1024' }, { src: 'https://images.unsplash.com/photo-1522985225914-17a10a58c8ec?dpr=2&auto=format&w=1024&h=1024', caption: 'Photo by Blake Cheek', } ] } const props2 = { onClose: () => {}, imgs: [ { src: 'https://images.unsplash.com/photo-1522931698295-e7b4d3e4188f?dpr=2&auto=format&w=1024&h=1024', thumnail: 'https://images.unsplash.com/photo-1522931698295-e7b4d3e4188f?dpr=2&auto=format&crop=faces&fit=crop&w=300&h=300', }, { src: 'https://images.unsplash.com/photo-1482398650355-d4c6462afa0e?dpr=2&auto=format&w=1024&h=1024', thumnail: 'https://images.unsplash.com/photo-1482398650355-d4c6462afa0e?dpr=2&auto=format&crop=faces&fit=crop&w=240&h=159', } ] } it('sum', () => { expect(1 + 1).toBe(2) }) // it('renders correctly', () => { // const component = mount( // // ) // component.setState({ isOpen: true }) // expect(component).toMatchSnapshot() // }) // it('renders with thumbnail correctly', () => { // const component = mount( // // ) // component.setState({ isOpen: true }) // expect(component).toMatchSnapshot() // }) // it('renders correctly after click', () => { // const component = mount( // // ) // component.setState({ isOpen: true }) // setTimeout(() => { // component.find('figure').first() && component.find('figure').first().simulate('click') // expect(component).toMatchSnapshot() // }, 10000) // }) // it('unmount', () => { // const component = mount( // // ) // component.setState({ isOpen: true }) // component.unmount() // }) ================================================ FILE: __test__/jestsetup.js ================================================ import { configure } from 'enzyme' import Adapter from 'enzyme-adapter-react-16' configure({ adapter: new Adapter() }) ================================================ FILE: __test__/rafShim.js ================================================ global.requestAnimationFrame = callback => setTimeout(callback, 0) ================================================ FILE: __test__/utils.test.js ================================================ import { deepMerge, canUseDom, bindFunctions } from '../src/utils/util' describe('the deepMerge function', () => { test('merge numb, stri in one level', () => expect( deepMerge({ a: 1, b: 2 }, { a: '3', b: 4 })).toEqual({ a: '3', b: 4 }) ) test('replace str with arr', () => expect( deepMerge({ a: 1 }, { a: ['3', 4] })).toEqual({ a: { 0: '3', 1: 4} }) ) test('replace arr with obj', () => expect( deepMerge({ a: [1, 2] }, { a: { b: 1, c: '2' } })).toEqual({ a: { 0: 1, 1: 2, b: 1, c: '2' } }) ) test('replace arr with obj', () => expect( deepMerge({ a: [1, 2] }, { a: { b: 1, c: '2' } })).toEqual({ a: { 0: 1, 1: 2, b: 1, c: '2' } }) ) test('replace obj with obj', () => expect( deepMerge({ a: { b: 1 } }, { a: { b: '2' } })).toEqual({ a: { b: '2' } }) ) test('replace arr with arr', () => expect( deepMerge({ a: [1] }, { a: { b: [2, 3] } })).toEqual({ a: { 0: 1, b: [2, 3] } }) ) test('replace obj with num', () => expect( deepMerge({ a: 1 }, 2)).toEqual({ a: 1 }) ) test('replace num with num', () => expect( deepMerge(1, 2)).toEqual({ }) ) }) ================================================ FILE: ci.sh ================================================ #!/bin/bash -e # daily push, but add all unStaged changes. commit_msg="$1" git add . git commit -m "$commit_msg" git pull git push ================================================ FILE: examples/src/.gitignore ================================================ ## This file is here to ensure it is included in the gh-pages branch, ## when 'gulp deploy' is used to push updates to the demo site. # Dependency directory node_modules ================================================ FILE: examples/src/.npmignore ================================================ ================================================ FILE: examples/src/app.js ================================================ import React from "react"; import { render } from "react-dom"; import Gallery from "./components/Gallery"; import CustomSpinner from "./components/Spinner"; import "./example.less"; function makeUnsplashSrc(id) { return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=1024&h=1024`; } function makeUnsplashSrcSet(id, size) { return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=${size} ${size}w`; } function makeUnsplashThumbnail(id, orientation = "landscape") { const dimensions = orientation === "square" ? "w=300&h=300" : "w=240&h=159"; return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&crop=faces&fit=crop&${dimensions}`; } // Unsplash images from the "Adventure" collection // https://unsplash.com/collections/369/adventure const DEFAULT_IMAGES = [ { id: "1526382551041-3c817fc3d478", caption: "Photo by Simon Alexander", orientation: "square", useForDemo: true, }, { id: "1522985225914-17a10a58c8ec", caption: "Photo by Blake Cheek", orientation: "square", useForDemo: true, }, { id: "1522931698295-e7b4d3e4188f", caption: "Photo by Oliver Sjöström", orientation: "square", useForDemo: true, }, { id: "1516175663209-ac2459a5652f", caption: "Photo by Jeremy Bishop", orientation: "square", useForDemo: true, }, { id: "1515911601378-97de98db6dda", caption: "Photo by Emily Reider", orientation: "square", useForDemo: true, }, ]; const THEMED_IMAGES = [ { id: "1506773090264-ac0b07293a64", caption: "Photo by Dan Grinwis", orientation: "square", useForDemo: true, }, { id: "1482398650355-d4c6462afa0e", caption: "Photo by Andrew Neel", orientation: "landscape", useForDemo: true, }, { id: "1514949823529-bdcc933a9339", caption: "Photo by Kristopher Roller", orientation: "landscape", useForDemo: true, }, { id: "1503293962593-47247718a17a", caption: "Photo by Jeremy Bishop", orientation: "landscape", useForDemo: true, }, { id: "1509914398892-963f53e6e2f1", caption: "Photo by Linus Nylund", orientation: "landscape", useForDemo: true, }, ]; const THUMBNAIL_IMAGES = [ { id: "1501963422762-3d89bd989568", caption: "Photo by Jeremy Bishop", orientation: "landscape", useForDemo: true, }, { id: "1418846531910-2b7bb1043512", caption: "Photo by Vincentiu Solomon", orientation: "landscape", useForDemo: true, }, { id: "1509529711801-deac231925ac", caption: "Photo by Joshua Earle", orientation: "landscape", useForDemo: true, }, { id: "1499062229216-7c6349e898fb", caption: "Photo by Leio McLaren", orientation: "square", useForDemo: true, }, { id: "1495619744764-2cc11fcbe5f0", caption: "Photo by Philipp Kämmerer", orientation: "square", useForDemo: true, }, { id: "1488584433697-7ccc1148d30c", caption: "Photo by Flecher Clay", orientation: "square", }, { id: "1478562853135-c3c9e3ef7905", caption: "Photo by Austin Neil", orientation: "landscape", }, { id: "1476111021705-ac3b3304fe20", caption: "Photo by Dino Reichmuth", orientation: "square", }, { id: "1478001517127-fccc92f54906", caption: "Photo by Joshua Earle", orientation: "landscape", }, { id: "1455383333344-451b6147021b", caption: "Photo by Joshua Earle", orientation: "landscape", }, ]; const theme = { // container container: { background: "rgba(255, 255, 255, .9)", }, // arrows arrow: { backgroundColor: "rgba(255, 255, 255, .8)", fill: "#222", opacity: 0.6, transition: "opacity 200ms", ":hover": { opacity: 1, }, }, arrow__size__medium: { borderRadius: 40, height: 40, marginTop: -20, "@media (min-width: 768px)": { height: 70, padding: 15, }, }, arrow__direction__left: { marginLeft: 10 }, arrow__direction__right: { marginRight: 10 }, close: { fill: "#d40000", opacity: 0.6, transition: "all 200ms", ":hover": { opacity: 1, }, }, // footer footer: { color: "#000", }, footerCount: { color: "rgba(0, 0, 0, .6)", }, // thumbnails thumbnail: {}, thumbnail__active: { boxShadow: "0 0 0 2px #00d8ff", }, }; // const theme = { // container: { // background: "rgba(255, 255, 255, .9)", // }, // arrow: { // backgroundColor: "#222", // fill: "#222", // opacity: 0.6, // transition: "opacity 200ms", // marginLeft: 300, // }, // arrow__size__medium: { // borderRadius: 40, // height: 40, // marginTop: -20, // marginLeft: 300, // }, // arrow__direction__left: { marginLeft: 300, background: "blue" }, // arrow__direction__right: { marginRight: 10, background: "blue" }, // close: { // fill: "#d40000", // opacity: 0.6, // transition: "all 200ms", // }, // footer: { // color: "#000", // }, // footerCount: { // color: "rgba(0, 0, 0, .6)", // }, // thumbnail: { // activeBorderColor: "#fff", // size: 50, // gutter: 2, // }, // thumbnail__active: { // boxShadow: "0 0 0 2px #00d8ff", // }, // }; render(

Photos courtesy of{" "} Unsplash . Use your keyboard to navigate left, up{" "} right, down space, esc — Also, try resizing your browser window.

Default Options

({ src: makeUnsplashSrc(id), thumbnail: makeUnsplashThumbnail(id, orientation), srcSet: [ makeUnsplashSrcSet(id, 1024), makeUnsplashSrcSet(id, 800), makeUnsplashSrcSet(id, 500), makeUnsplashSrcSet(id, 320), ], caption, orientation, useForDemo, }))} />

With Thumbnails

({ src: makeUnsplashSrc(id), thumbnail: makeUnsplashThumbnail(id, orientation), srcSet: [ makeUnsplashSrcSet(id, 1024), makeUnsplashSrcSet(id, 800), makeUnsplashSrcSet(id, 500), makeUnsplashSrcSet(id, 320), ], caption, orientation, useForDemo, }) )} showThumbnails />

Themed ImgsViewer

({ src: makeUnsplashSrc(id), thumbnail: makeUnsplashThumbnail(id, orientation), srcSet: [ makeUnsplashSrcSet(id, 1024), makeUnsplashSrcSet(id, 800), makeUnsplashSrcSet(id, 500), makeUnsplashSrcSet(id, 320), ], caption, orientation, useForDemo, }))} theme={theme} spinner={CustomSpinner} spinnerColor={"#d40000"} spinnerSize={150} showThumbnails />
, document.getElementById("example") ); ================================================ FILE: examples/src/app_CN.js ================================================ import React from 'react' import { render } from 'react-dom' import Gallery from './components/Gallery' import CustomSpinner from './components/Spinner' import './example.less' function makeUnsplashSrc (id) { return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=1024&h=1024` } function makeUnsplashSrcSet(id, size) { return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=${size} ${size}w` } function makeUnsplashThumbnail (id, orientation = 'landscape') { const dimensions = orientation === 'square' ? 'w=300&h=300' : 'w=240&h=159' return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&crop=faces&fit=crop&${dimensions}` } // Unsplash images from the "Adventure" collection // https://unsplash.com/collections/369/adventure const DEFAULT_IMAGES = [ { id: '1526382551041-3c817fc3d478', caption: '照片来自Simon Alexander', orientation: 'square', useForDemo: true }, { id: '1522985225914-17a10a58c8ec', caption: '照片来自Blake Cheek', orientation: 'square', useForDemo: true }, { id: '1522931698295-e7b4d3e4188f', caption: '照片来自Oliver Sjöström', orientation: 'square', useForDemo: true }, { id: '1516175663209-ac2459a5652f', caption: '照片来自Jeremy Bishop', orientation: 'square', useForDemo: true }, { id: '1515911601378-97de98db6dda', caption: '照片来自Emily Reider', orientation: 'square', useForDemo: true }, ] const THEMED_IMAGES = [ { id: '1506773090264-ac0b07293a64', caption: '照片来自Dan Grinwis', orientation: 'square', useForDemo: true }, { id: '1482398650355-d4c6462afa0e', caption: '照片来自Andrew Neel', orientation: 'landscape', useForDemo: true }, { id: '1514949823529-bdcc933a9339', caption: '照片来自Kristopher Roller', orientation: 'landscape', useForDemo: true }, { id: '1503293962593-47247718a17a', caption: '照片来自Jeremy Bishop', orientation: 'landscape', useForDemo: true }, { id: '1509914398892-963f53e6e2f1', caption: '照片来自Linus Nylund', orientation: 'landscape', useForDemo: true }, ] const THUMBNAIL_IMAGES = [ { id: '1501963422762-3d89bd989568', caption: '照片来自Jeremy Bishop', orientation: 'landscape', useForDemo: true }, { id: '1418846531910-2b7bb1043512', caption: '照片来自Vincentiu Solomon', orientation: 'landscape', useForDemo: true }, { id: '1509529711801-deac231925ac', caption: '照片来自Joshua Earle', orientation: 'landscape', useForDemo: true }, { id: '1499062229216-7c6349e898fb', caption: '照片来自Leio McLaren', orientation: 'square', useForDemo: true }, { id: '1495619744764-2cc11fcbe5f0', caption: '照片来自Philipp Kämmerer', orientation: 'square', useForDemo: true }, { id: '1488584433697-7ccc1148d30c', caption: '照片来自Flecher Clay', orientation: 'square' }, { id: '1478562853135-c3c9e3ef7905', caption: '照片来自Austin Neil', orientation: 'landscape' }, { id: '1476111021705-ac3b3304fe20', caption: '照片来自Dino Reichmuth', orientation: 'square' }, { id: '1478001517127-fccc92f54906', caption: '照片来自Joshua Earle', orientation: 'landscape' }, { id: '1455383333344-451b6147021b', caption: '照片来自Joshua Earle', orientation: 'landscape' }, ] const theme = { // container container: { background: 'rgba(255, 255, 255, .9)' }, // arrows arrow: { backgroundColor: 'rgba(255, 255, 255, .8)', fill: '#222', opacity: .6, transition: 'opacity 200ms', ':hover': { opacity: 1, } }, arrow__size__medium: { borderRadius: 40, height: 40, marginTop: -20, '@media (min-width: 768px)': { height: 70, padding: 15, } }, arrow__direction__left: { marginLeft: 10 }, arrow__direction__right: { marginRight: 10 }, close: { fill: '#d40000', opacity: .6, transition: 'all 200ms', ':hover': { opacity: 1 } }, // footer footer: { color: '#000' }, footerCount: { color: 'rgba(0, 0, 0, .6)' }, // thumbnails thumbnail:{ }, thumbnail__active: { boxShadow: '0 0 0 2px #00d8ff' } }; render(

照片均来自 Unsplash. 采用以下键盘按钮来导航: 向上,向左(上一张) 向下,向右(下一张) 空格,Esc(关闭) — 你也可以尝试改变一下浏览器窗口大小~

默认选项

({ src: makeUnsplashSrc(id), thumbnail: makeUnsplashThumbnail(id, orientation), srcSet: [ makeUnsplashSrcSet(id, 1024), makeUnsplashSrcSet(id, 800), makeUnsplashSrcSet(id, 500), makeUnsplashSrcSet(id, 320) ], caption, orientation, useForDemo, }))} />

缩略图同步

({ src: makeUnsplashSrc(id), thumbnail: makeUnsplashThumbnail(id, orientation), srcSet: [ makeUnsplashSrcSet(id, 1024), makeUnsplashSrcSet(id, 800), makeUnsplashSrcSet(id, 500), makeUnsplashSrcSet(id, 320) ], caption, orientation, useForDemo, }))} showThumbnails />

主题可定制

({ src: makeUnsplashSrc(id), thumbnail: makeUnsplashThumbnail(id, orientation), srcSet: [ makeUnsplashSrcSet(id, 1024), makeUnsplashSrcSet(id, 800), makeUnsplashSrcSet(id, 500), makeUnsplashSrcSet(id, 320) ], caption, orientation, useForDemo, }))} theme={theme} spinner={CustomSpinner} spinnerColor={'#d40000'} spinnerSize={150} showThumbnails />
, document.getElementById('example-CN') ) ================================================ FILE: examples/src/components/DownloadButton/icon.js ================================================ module.exports = ( '' + '' + '' + '' + '' + '' ); ================================================ FILE: examples/src/components/DownloadButton/index.js ================================================ import PropTypes from 'prop-types' import React, { Component } from 'react' import DownloadIcon from './icon' class DownloadButton extends Component { constructor () { super() } render () { return ( ) } } export default DownloadButton ================================================ FILE: examples/src/components/Gallery.js ================================================ import React, { Component } from 'react' import ImgsViewer from 'react-images-viewer' import PropTypes from 'prop-types' import { css, StyleSheet } from 'aphrodite/no-important' class Gallery extends Component { constructor () { super() this.state = { isOpen: false, currImg: 0, } this.gotoNext = this.gotoNext.bind(this) this.gotoPrev = this.gotoPrev.bind(this) this.gotoImg = this.gotoImg.bind(this) this.handleClickImg = this.handleClickImg.bind(this) this.closeImgsViewer = this.closeImgsViewer.bind(this) this.openImgsViewer = this.openImgsViewer.bind(this) } openImgsViewer (index, event) { event.preventDefault() this.setState({ currImg: index, isOpen: true, }) } closeImgsViewer () { this.setState({ currImg: 0, isOpen: false, }) } gotoPrev () { this.setState({ currImg: this.state.currImg - 1 }) } gotoNext () { this.setState({ currImg: this.state.currImg + 1 }) } gotoImg (index) { this.setState({ currImg: index }) } handleClickImg () { if (this.state.currImg === this.props.imgs.length - 1) return this.gotoNext() } renderGallery () { const { imgs } = this.props if (!imgs) return const gallery = imgs.filter(i => i.useForDemo).map((obj, i) => { return ( this.openImgsViewer(i, e)} > ) }) return (
{gallery}
) } render () { return (
{this.props.heading &&

{this.props.heading}

} {this.props.subheading &&

{this.props.subheading}

} {this.renderGallery()}
) } } Gallery.displayName = 'Gallery' Gallery.propTypes = { preventScroll: PropTypes.bool, spinner: PropTypes.func, spinnerColor: PropTypes.string, spinnerSize: PropTypes.number, theme: PropTypes.object, heading: PropTypes.string, imgs: PropTypes.array, showThumbnails: PropTypes.bool, subheading: PropTypes.string, } const gutter = { small: 2, large: 4, } const classes = StyleSheet.create({ gallery: { marginRight: -gutter.small, overflow: 'hidden', '@media (min-width: 500px)': { marginRight: -gutter.large, } }, // anchor thumbnail: { boxSizing: 'border-box', display: 'block', float: 'left', lineHeight: 0, paddingRight: gutter.small, paddingBottom: gutter.small, overflow: 'hidden', '@media (min-width: 500px)': { paddingRight: gutter.large, paddingBottom: gutter.large, } }, // orientation landscape: { width: '30%', }, square: { paddingBottom: gutter.large, width: '40%', '@media (min-width: 500px)': { paddingBottom: gutter.large, } }, // actual source: { border: 0, display: 'block', height: 'auto', maxWidth: '100%', width: 'auto' }, }) export default Gallery ================================================ FILE: examples/src/components/Spinner.js ================================================ import PropTypes from 'prop-types' import React from 'react' import { css, StyleSheet } from 'aphrodite/no-important' const Spinner = (props) => { const classes = StyleSheet.create(styles(props)) return (
) } Spinner.propTypes = { color: PropTypes.string, size: PropTypes.number, } const squareKeyframes = { '0%': { top: 0, left: '25%', opacity: 1, }, '25%': { top: '50%', left: '50%', opacity: .75, }, '75%': { top: '50%', left: 0, opacity: .5, }, '100%': { top: 0, left: '25%', opacity: 1, } } const styles = ({ color, size }) => ({ spinner: { display: 'inline-block', position: 'relative', width: size, height: size, }, square: { position: 'absolute', width: size / 10, height: size / 10, border: `4px solid ${color}`, borderRadius: '50%', background: color, animationName: squareKeyframes, animationDuration: '2s', animationTimingFunction: 'linear', animationIterationCount: 'infinite', } }) export default Spinner ================================================ FILE: examples/src/example.less ================================================ // Cmomon Example Styles // Constans // example site @gutter: 20px; @table-cell-gutter: 10px; @left-col-width: 180px; @heading-color: #000; @kbd-bg-color: #fafafa; @kbd-border-color: #ccc; @link-color: #00d7ff; @text-color: #333; @nav-gutter: 30px; @nav-item-padding: 5px; @nav-padding: @nav-gutter - @nav-item-padding; // Base body { background-color: '#fff'; color: @text-color; font-family: 'Misrosoft Yahei', Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; } a { color: @link-color; text-decoration: none; &:hover { text-decoration: underline; } } hr { border: none; border-top: 1px solid rgba(0, 0, 0, .1); margin-top: 2em; margin-bottom: 2em; } .container { margin-right: auto; margin-left: auto; max-width: 940px; padding: 0 @gutter; } // Columns .left-col { display: none; float: left; position: fixed; width: @left-col-width; z-index: 1; } // Navigation .page-nav { list-style: none; margin: 0; padding: 0 @nav-padding 0 0; text-align: right; } .page-nav__item { } .page-nav__link { display: block; padding: @nav-item-padding; } // Headings h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { color: @heading-color; font-weight: 500; line-height: 1; margin-bottom: 0.66em; margin-top: 0; } h1, .h1 { font-size: 3em; } h2, .h2 { font-size: 2em; font-weight: 300; padding-top: @nav-gutter; } h3, .h3 { color: #999; font-size: 1em; font-weight: bold; margin-top: 2em; text-transform: uppercase; } h4, .h4 { font-size: 1em; } h5, .h5 { font-size: .85em; } h6, .h6 { font-size: .75em; } // Layout // ------------------------------ // header .page-header { padding: @gutter 0; p { margin: 0; } a { border-bottom: 1px solid fade(white, 30%); text-decoration: none; &:hover, &:focus { border-bottom-color: white; outline: none; text-decoration: none; } } } .page-header__title { font-style: 3em; } .page-header__subtitle { color: #777777; font-size: 1.125em; font-weight: 300; } // subheader .page-subheader { background-color: mix(@link-color, white, 8%); border-radius: 4px; padding: @gutter; } .page-subheader__button { float: right; } .page-subheader__link { border-bottom: 1px solid fade(@link-color, 30%); outline: none; text-decoration: none; &:hover, &:focus { border-bottom-color: @link-color; outline: none; text-decoration: none; } } // content .page-content { padding-bottom: @nav-gutter; } // footer .page-footer { background-color: #fafafa; color: #999; padding: @nav-gutter 0; text-align: center; } // options table .options-table { border-collapse: collapse; border-spacing: 0; margin-left: -@table-cell-gutter; margin-right: -@table-cell-gutter; min-width: 100%; width: 100%; td, th { padding: @table-cell-gutter; } th { border-bottom: 1px solid rgba(0, 0, 0, .05); color: #999999; font-weight: normal; } td { border-top: 1px solid rgba(0, 0, 0, .05); } } // Miscellaneous // -------------------- .section { margin-bottom: 40px; } .hint { font-size: .85em; margin: 15px 0; color: #666; } kbd { background-color: @kbd-bg-color; border-radius: 3px; border: 1px solid @kbd-border-color; border-bottom-color: darken(@kbd-border-color, 4%); border-top-color: lighten(@kbd-border-color, 4%); box-shadow: 0 1px 1px rgba(0, 0, 0, .12), 0 2px 0 0 rgba(255, 255, 255, .7) inset; color: #666666; display: inline-block; font-family: 'Microsoft Yahei'; font-size: .85em; font-weight: 500; line-height: inherit; padding: 1px 4px; white-space: nowrap; // little hack to tweak 'visual-middle' alignment position: relative; top: -1px; } pre { background-color: #eeeeee; border-radius: 4px; margin-bottom: @nav-gutter; } // Responsive // -------------------- // largee phones and up @media (min-width: 481px) { .page-header, .page-nav { padding-bottom: @nav-gutter; padding-top: @nav-gutter; } .page-header__title { font-size: 4em; } .page-subheader { font-size: 1.125em; line-height: 28px; } } // large phones and down @media (max-width: 768px) { .options-table-container { overflow-x: scroll; overflow-y: hidden; width: 100%; -ms-overflow-style: -ms-autohiding-scrollbar; -webkit-overflow-scrolling: touch; } .options-table { table-layout: auto; td { white-space: nowrap; } } } // tablets and up @media (min-width: 769px) { .left-col { display: block; } .right-col { padding-left: @left-col-width; } .page-content { padding-left: @nav-gutter; position: relative; &::before { background: linear-gradient(90deg, fade(#e9e9e9, 0%) 94%, #e9e9e9), linear-gradient(90deg, fade(#f6f6f6, 0%) 50%, #f6f6f6); border-right: 1px solid #e6e6e6; content: " "; margin-left: -(@left-col-width + @nav-gutter); height: 100%; position: fixed; width: @left-col-width; } } .page-header__subtitle { font-size: 2em; } .page-footer { border: none; bottom: 0; float: left; margin-left: -@nav-gutter; position: fixed; text-align: right; width: @left-col-width; z-index: 1; } .page-footer__copyright--small { display: none; } } ================================================ FILE: examples/src/index.html ================================================ React-Images-Viewer

Examples

Getting Started

1. Install using yarn:

yarn add react-images-viewer

… or npm:

npm install react-images-viewer --save

2. Example usage with JSX:

                
  <ImgsViewer
    imgs={[
      { src: '../images/photo-1.jpg' },
      { src: '../images/photo-2.jpg' }
    ]}
    isOpen={this.state.isOpen}
    onClickPrev={this.gotoPrevImg}
    onClickNext={this.gotoNextImg}
    onClose={this.closeImgsViewer}
  />
                
              

Options

Property Type Default Description
backdropCloseable bool false Allow users to exit the imgsViewer by clicking the backdrop
currImg number 0 The index of the image to display initially
customControls array undefined An array of elements to display as custom controls on the top of imgsViewer
enableKeyboardInput bool true Supports keyboard input - space, esc, arrow left, arrow up, and arrow right, arrow down
imgs array undefined Required. An array of objects containing valid src and srcset values of img element
imgCountSeparator string ' / ' Custom separator for the image count
isOpen bool false Whether or not the imgsViewer is displayed
onClickPrev func undefined Fired on request of the previous image
onClickNext func undefined Fired on request of the next image
onClickImg func undefined Handle click event on the current image
onClickThumbnail func undefined Handle click on thumbnail
onClose func undefined Required. Handle closing of the imgsViewer
preloadNextImg bool true Whether to preload the next available image.
preventAutoScroll bool true Determines whether auto-scrolling is prevented
showCloseBtn bool true Optionally display a close "X" button in top right corner
showImgCount bool true Optionally display image index, e.g., "3 of 20"
showThumbnails bool false Optionally display thumbnails beneath the imgsViewer
theme object undefined Pass through styles to theme each component; arrow, container, etc.
width number 1024 Maximum width of the carousel; defaults to 1024px

Images

Property Type Default Description
src str undefined Required. The primary image path
srcset arr undefined List of alternative image sizes
caption str undefined Displayed benath the current image. Great for description or attribution
thumbnail str undefined Thumbnail to display if showThumbnails is set to true

License

React Images Viewer is free to use for personal and commercial projects under the MIT license.

Attribution is not required, but greatly appreciated. It does not have to be user-facing and can remain within the code.

Help

Have a question?

Follow the quick start guide on GitHub to get up and running quickly. Please do not use Github Issues to report personal support requests.

Found a bug?

If you find a bug, please read the Contribution Guildelines before you report the issue.

================================================ FILE: examples/src/index_CN.html ================================================ React-Images-Viewer

例子

入门

1. 依靠 yarn 来安装

yarn add react-images

… 或者 npm:

npm install react-images --save

2. JSX 使用案例:

                
  <Viewer
    imgs={[
      { src: '../images/photo-1.jpg' },
      { src: '../images/photo-2.jpg' }
    ]}
    isOpen={this.state.isOpen}
    onClickPrev={this.gotoPrevImg}
    onClickNext={this.gotoNextImg}
    onClose={this.closeImgsViewer}
  />
                
              

选项

属性名 类型 默认值 描述
backdropCloseable 布尔值 false 是否可以通过点击幕布退出查看
currImg 数值型 0 当前图片初始化索引
customControls 数组 undefined 照片查看器顶部控件的元素数组
enableKeyboardInput 布尔值 true 支持键盘输入 - 空格键,Esc, 左箭头,上箭头, and 右箭头,下箭头
imgs 数组 undefined 必须项。一个包含具备img 元素src,srcset 合法值的对象数组。
imgCountSeparator string ' / ' 图片计数器分隔符
isOpen 布尔值 false 图片查看器是否非法
onClickPrev func undefined 请求上一张图片时触发
onClickNext func undefined 请求下一张图片时触发
onClickImg func undefined 当前图片的点击事件处理器
onClickThumbnail func undefined 缩略图的点击事件处理器
onClose func undefined 必须项,照片查看器的关闭事件处理器
preloadNextImg 布尔值 true 是否预加载下一张图片
preventScroll 布尔值 true 自动滚动是否阻止
showCloseBtn 布尔值 true 是否在右上角显示 X 关闭按钮
showImgCount 布尔值 true 是否显示图片索引,比如“3 / 20”
showThumbnails 布尔值 false 是否在查看器下方显示缩略图
theme 对象 undefined 传递给每个组件的主题化样式; arrow键, container键,等等。
width 数值型 1024 轮播器的最大宽度,默认为1024px

Imgs

属性名 类型 默认值 描述
src 字符串 undefined 必须项. 主图片路径
srcset arr undefined 可替代图片尺寸列表
caption 字符串 undefined 当前图片下显示,对于描述或者归属很有用。
thumbnail 字符串 undefined showThumbnails被设置成 true的话要显示的缩略图

许可证

React Images Viewer在 MIT证书下,对于个人或者商业用途都是免费的

它不是必须项,但是十分感谢。没有面向用户而且保留源代码。

帮助

需要帮助?

遵循 github 上的快速入门指南,并运行起来。 请不要出于个人目的滥用 github issues。

发现 bug?

如果你找到 bug 了,请在你 报告issue.

之前阅读贡献指南一番。
================================================ FILE: examples/src/standalone.html ================================================ React-Images-Viewer
================================================ FILE: index.js ================================================ 'use strict'; module.exports = function(number, locale) { return number.toLocaleString(locale); }; ================================================ FILE: jest.config.js ================================================ module.exports = { verbose: true, collectCoverage: true, collectCoverageFrom: ['src/*.{js, jsx}'], setupFiles: ['./__test__/rafShim.js', './__test__/jestsetup.js'], testURL: 'http://localhost/', // moduleFileExtensions: [ // 'js', // 'jsx', // ], // moduleNameMapper: { // '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/__mocks__/fileMock.js', // '\\.(css|less)$': '/__mocks__/styleMock.js', // }, // transform: { // '^.+\\.js$': 'babel-jest', // } } ================================================ FILE: new-branch.sh ================================================ git checkout -b $1 git push -u origin $1 git branch --all ================================================ FILE: package-scripts.js ================================================ const npsUtils = require('nps-utils') const path = require('path') const { series, rimraf, concurrent } = npsUtils module.exports = { scripts: { build: { description: 'clean dist directory and run all builds', default: series( rimraf('dist'), rimraf('lib'), concurrent.nps('build.rollup', 'build.babel', 'build.less') ), rollup: 'rollup --config', babel: 'babel src -d lib', less: series( 'lessc examples/src/example.less examples/dist/example.css' ), }, publish: { default: series( rimraf('examples/dist'), 'webpack --progress -p', // 'git pull', 'git subtree push --prefix examples/dist origin gh-pages' // 'git push origin `git subtree split --prefix examples/dist gh-pages`:gh-pages --force' ) } } } ================================================ FILE: package.json ================================================ { "name": "react-images-viewer", "version": "1.7.1", "description": "Create an react-images-viewer component.", "main": "lib/ImgsViewer.js", "jsnext:main": "dist/react-images-viewer.es.js", "scripts": { "build": "nps build", "publish:examples": "NODE_ENV=production nps publish", "start": "webpack-dev-server --progress", "lint": "eslint src/** examples/src/app.js examples/src/components/Gallery.js", "test": "jest", "coverage": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" }, "repository": { "type": "git", "url": "git+https://github.com/guonanci/react-images-viewer.git" }, "keywords": [ "react", "react-component", "react images", "react-images", "react-images-viewer", "lightbox", "react lightbox", "react-lightbox", "carousel", "react-carousel", "react carousel", "gallery", "react-gallery", "react gallery", "react-photo", "react-photo-viewer" ], "author": "yiminanci (https://juejin.cn/user/3491704660305111)", "license": "MIT", "responsitory": { "type": "git", "url": "https://github.com/guonanci/react-images-viewer.git" }, "bugs": { "url": "https://github.com/guonanci/react-images-viewer/issues" }, "homepage": "https://github.com/guonanci/react-images-viewer#readme", "devDependencies": { "babel": "^6.23.0", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-eslint": "^8.2.5", "babel-jest": "^23.4.2", "babel-loader": "^7.1.5", "babel-plugin-external-helpers": "^6.22.0", "babel-plugin-transform-object-assign": "^6.22.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.13", "babel-preset-env": "^1.7.0", "babel-preset-react": "^6.24.1", "coveralls": "^3.0.2", "css-loader": "^1.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.15.6", "eslint": "^5.1.0", "eslint-plugin-react": "^7.10.0", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "jest": "^23.4.2", "jest-cli": "^23.4.2", "less": "^3.5.3", "less-loader": "^4.1.0", "mini-css-extract-plugin": "^0.4.1", "nps": "^5.9.2", "nps-utils": "^1.6.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-test-renderer": "^16.4.1", "regenerator-runtime": "^0.12.0", "rollup": "^0.62.0", "rollup-plugin-babel": "^3.0.7", "rollup-plugin-commonjs": "^9.1.3", "rollup-plugin-node-resolve": "^3.3.0", "rollup-plugin-uglify": "^4.0.0", "style-loader": "^0.21.0", "uglify-es": "^3.3.9", "webpack": "^4.16.2", "webpack-cli": "^3.1.0", "webpack-dev-server": "^3.1.5" }, "peerDependencies": { "react": "^15.0 || ^16.0 || ^17.0", "react-dom": "^15.0 || ^16.0 || ^17.0" }, "dependencies": { "aphrodite": "^2.2.2", "prop-types": "^15.6.2", "react-scrolllock": "^3.0.1", "react-transition-group": "^2.4.0" } } ================================================ FILE: rollup.config.js ================================================ import babel from 'rollup-plugin-babel' import resolve from 'rollup-plugin-node-resolve' import { uglify } from 'rollup-plugin-uglify' import { minify } from 'uglify-es' const name = 'ImgsViewer' const path = 'dist/react-images-viewer' const globals = { 'prop-types': 'PropTypes', 'react-dom': 'ReactDOM', react: 'React', aphrodite: 'aphrodite', 'aphrodite/no-important': 'aphrodite', 'react-scrolllock': 'ScrollLock', 'react-transition-group': 'ReactTransitionGroup', 'react-spinners': 'BounceLoader', } const external = Object.keys(globals) const babelOptions = (production) => { let result = { babelrc: false, presets: ['react', ['env', { modules: false }]], plugins: [['transform-object-rest-spread', { useBuiltIns: true }], 'transform-object-rest-spread', 'external-helpers'] } if (production) { result.plugins.push('transform-react-remove-prop-types') } return result } export default [ { input: 'src/ImgsViewer.js', output: { file: path + '.es.js', format: 'es', }, external: external, plugins: [babel(babelOptions(false))] }, { input: 'src/ImgsViewer.js', output: { name: name, file: path + '.js', format: 'umd', globals: globals, }, external: external, plugins: [babel(babelOptions(false)), resolve()] }, { input: 'src/ImgsViewer.js', output: { name: name, file: path + '.min.js', format: 'umd', globals: globals, }, external: external, plugins: [babel(babelOptions(true)), resolve(), uglify({}, minify)], } ] ================================================ FILE: src/ImgsViewer.js ================================================ import PropTypes from "prop-types"; import React, { Component, Fragment } from "react"; import { StyleSheet, css } from "aphrodite"; import ScrollLock from "react-scrolllock"; import defaultTheme from "./theme"; import Arrow from "./components/Arrow"; import Container from "./components/Container"; import Footer from "./components/Footer"; import Header from "./components/Header"; import PaginatedThumbnails from "./components/PaginatedThumbnails"; import Portal from "./components/Portal"; import DefaultSpinner from "./components/Spinner"; import { bindFunctions, canUseDom, deepMerge } from "./utils/util"; function normalizeSourceSet(data) { const sourceSet = data.srcSet || data.srcset; if (Array.isArray(sourceSet)) { return sourceSet.join(); } return sourceSet; } const ThemeContext = React.createContext({ theme: defaultTheme, toggleTheme: (newTheme) => {}, }); class ImgsViewer extends Component { constructor(props) { super(props); this.theme = deepMerge(defaultTheme, this.props.theme); this.classes = StyleSheet.create( deepMerge(defaultStyles, this.props.theme) ); this.toggleTheme = (theme) => { this.setState(() => ({ theme })); }; this.state = { imgLoaded: false, theme: this.theme, toggleTheme: this.toggleTheme, }; bindFunctions.call(this, [ "gotoNext", "gotoPrev", "closeBackdrop", "handleKeyboardInput", "handleImgLoaded", ]); } componentDidMount() { if (this.props.isOpen) { if (this.props.enableKeyboardInput) { window.addEventListener("keydown", this.handleKeyboardInput); } if (typeof this.props.currImg === "number") { this.preloadImg(this.props.currImg, this.handleImgLoaded); } } } // static getDerivedStateFromProps (nextProps, prevState) { UNSAFE_componentWillReceiveProps(nextProps) { if (!canUseDom) return; // const instance = this // always to preload imgs with both directions // then when user changs direction, img also show quickly if (nextProps.preloadNextImg) { const nextIdx = nextProps.currImg + 1; const prevIdx = nextProps.currImg - 1; // debugger // if (!this) return null this.preloadImg(prevIdx); this.preloadImg(nextIdx); } // preload currImg if ( this.props.currImg !== nextProps.currImg || (!this.props.isOpen && nextProps.isOpen) ) { const img = this.preloadImgData( nextProps.imgs[nextProps.currImg], this.handleImgLoaded ); if (img) this.setState({ imgLoaded: img.complete, }); } // add/remove event listeners if ( !this.props.isOpen && nextProps.isOpen && nextProps.enableKeyboardInput ) { window.addEventListener("keydown", this.handleKeyboardInput); } if (!nextProps.isOpen && nextProps.enableKeyboardInput) { window.removeEventListener("keydown", this.handleKeyboardInput); } return null; } componentWillUnmount() { if (this.props.enableKeyboardInput) { window.removeEventListener("keydown", this.handleKeyboardInput); } } // ==================== // Methods // ==================== preloadImg(idx, onload) { return this.preloadImgData(this.props.imgs[idx], onload); } preloadImgData(data, onload) { if (!data) return; const img = new Image(); const sourceSet = normalizeSourceSet(data); // Todo: add error handling for missing imgs img.onerror = onload; img.onload = onload; img.src = data.src; if (sourceSet) img.srcset = sourceSet; return img; } gotoNext(event) { const { currImg, imgs } = this.props; const { imgLoaded } = this.state; if (!imgLoaded || currImg === imgs.length - 1) return; if (event) { event.preventDefault(); event.stopPropagation(); } this.props.onClickNext(); } gotoPrev(event) { const { currImg } = this.props; const { imgLoaded } = this.state; if (!imgLoaded || currImg === 0) return; if (event) { event.preventDefault(); event.stopPropagation(); } this.props.onClickPrev(); } closeBackdrop(event) { if ( event.target.id === "viewerBackdrop" || event.target.tagName === "FIGURE" ) { this.props.onClose(); } } handleKeyboardInput(event) { const { keyCode } = event; if (keyCode === 37 || keyCode === 33 || keyCode === 38) { // left, pageup, up this.gotoPrev(event); return true; } else if (keyCode === 39 || keyCode === 34 || keyCode === 40) { // right, pagedown, down this.gotoNext(event); return true; } else if (keyCode === 27 || keyCode === 32) { // esc, space this.props.onClose(); return true; } return false; } handleImgLoaded() { this.setState({ imgLoaded: true, }); } // ==================== // Renderers // ==================== renderArrowPrev(theme) { if (this.props.currImg === 0) return null; return ( ); } renderArrowNext(theme) { if (this.props.currImg === this.props.imgs.length - 1) return null; return ( ); } renderDialog(newState) { const { backdropCloseable, isOpen, showThumbnails, width } = this.props; const { imgLoaded } = this.state; if (!isOpen) return ; const offsetThumbnails = showThumbnails ? this.theme.thumbnail.size + this.theme.container.gutter.vertical : 0; return ( {({ theme, toggleTheme }) => { theme = newState.theme; return (
{imgLoaded && this.renderHeader(theme)}{" "} {this.renderImgs(theme)} {this.renderSpinner()} {imgLoaded && this.renderFooter(theme)}
{imgLoaded && this.renderThumbnails(theme)} {imgLoaded && this.renderArrowPrev(theme)} {imgLoaded && this.renderArrowNext(theme)} {this.props.preventScroll && }
); }}
); } renderImgs(theme) { const { currImg, imgs, onClickImg, showThumbnails } = this.props; const { imgLoaded } = this.state; if (!imgs || !imgs.length) return null; const img = imgs[currImg]; const sourceSet = normalizeSourceSet(img); const sizes = sourceSet ? "100vw" : null; const thumbnailsSize = showThumbnails ? theme.thumbnail.size : 0; const heightOffset = `${ theme.header.height + theme.footer.height + thumbnailsSize + theme.container.gutter.vertical }px`; return (
{img.alt}
); } renderThumbnails(theme) { const { imgs, currImg, leftArrowTitle, rightArrowTitle, onClickThumbnail, showThumbnails, thumbnailOffset, } = this.props; if (!showThumbnails) return null; return ( ); } renderHeader(theme) { const { closeBtnTitle, customControls, onClose, showCloseBtn } = this.props; return (
); } renderFooter(theme) { const { currImg, imgs, imgCountSeparator, showImgCount } = this.props; if (!imgs || !imgs.length) return null; return (