├── public ├── close.ico ├── favicon.ico ├── rotate2.png ├── manifest.json └── index.html ├── src ├── imageModal │ ├── images │ │ ├── close.png │ │ └── rotate.png │ ├── common │ │ ├── zoom.js │ │ ├── onkeydown.js │ │ ├── watermark.js │ │ └── move.js │ ├── index.css │ └── index.js ├── modal_lib │ ├── src │ │ └── imageModal │ │ │ ├── images │ │ │ ├── close.png │ │ │ └── rotate.png │ │ │ ├── common │ │ │ ├── zoom.js │ │ │ ├── onkeydown.js │ │ │ ├── move.js │ │ │ └── watermark.js │ │ │ ├── index.css │ │ │ └── index.js │ ├── package.json │ ├── index.js │ └── readme.md ├── index.css ├── App.js ├── index.js ├── logo.svg ├── Container.js └── serviceWorker.js ├── .gitignore ├── config ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── scripts ├── test.js ├── start.js └── build.js ├── package.json └── README.md /public/close.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/public/close.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/rotate2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/public/rotate2.png -------------------------------------------------------------------------------- /src/imageModal/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/src/imageModal/images/close.png -------------------------------------------------------------------------------- /src/imageModal/images/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/src/imageModal/images/rotate.png -------------------------------------------------------------------------------- /src/modal_lib/src/imageModal/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/src/modal_lib/src/imageModal/images/close.png -------------------------------------------------------------------------------- /src/modal_lib/src/imageModal/images/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wtfjun/cxjReactImage/HEAD/src/modal_lib/src/imageModal/images/rotate.png -------------------------------------------------------------------------------- /src/modal_lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cxj-react-image", 3 | "version": "1.0.13", 4 | "description": "A simple image modal by react.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "wtfjun", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /src/modal_lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _imageModal = require('./src/imageModal'); 8 | 9 | var _imageModal2 = _interopRequireDefault(_imageModal); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = _imageModal2.default; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .image-content { 2 | position: relative; 3 | overflow: hidden; 4 | } 5 | 6 | .image-item-menu:hover { 7 | opacity: 1; 8 | } 9 | .cxj-image-modal img { 10 | transition: all 0.3s; 11 | width: 100%; 12 | } 13 | 14 | .image-list { 15 | list-style-type: none; 16 | } 17 | 18 | .image-list li { 19 | float: left; 20 | margin-right: 10px; 21 | } 22 | 23 | .image-list li:hover { 24 | cursor: pointer; 25 | } 26 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Container from './Container'; 3 | 4 | class App extends Component { 5 | render() { 6 | return ( 7 |
8 | 9 | github地址: 10 | 11 | https://github.com/wtfjun/cxjReactImage 12 | 13 |
14 | ); 15 | } 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/imageModal/common/zoom.js: -------------------------------------------------------------------------------- 1 | // 控制滚轮缩放 2 | 3 | const zoom = (onWheelEvent, dom) => { 4 | let e = onWheelEvent; 5 | let imageModalWidth = parseInt(dom.style.width); 6 | let modalLeft = parseInt(dom.style.left); 7 | 8 | // 计算缩放后的大小 每一次滚轮 100px 9 | let calcWidth = imageModalWidth - e.deltaY; 10 | 11 | // 限制最小 width = 400 12 | if (calcWidth <= 300) { 13 | return; 14 | } 15 | 16 | // 不让modal由于缩小消失在视野中 17 | if (modalLeft + calcWidth < 50) { 18 | return; 19 | } 20 | 21 | dom.style.width = `${calcWidth}px`; 22 | }; 23 | 24 | export default zoom; 25 | -------------------------------------------------------------------------------- /src/modal_lib/src/imageModal/common/zoom.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | // 控制滚轮缩放 7 | 8 | var zoom = function zoom(onWheelEvent, dom) { 9 | var e = onWheelEvent; 10 | var imageModalWidth = parseInt(dom.style.width); 11 | var modalLeft = parseInt(dom.style.left); 12 | 13 | // 计算缩放后的大小 每一次滚轮 100px 14 | var calcWidth = imageModalWidth - e.deltaY; 15 | 16 | // 限制最小 width = 400 17 | if (calcWidth <= 300) { 18 | return; 19 | } 20 | 21 | // 不让modal由于缩小消失在视野中 22 | if (modalLeft + calcWidth < 50) { 23 | return; 24 | } 25 | 26 | dom.style.width = calcWidth + "px"; 27 | }; 28 | 29 | exports.default = zoom; -------------------------------------------------------------------------------- /src/imageModal/common/onkeydown.js: -------------------------------------------------------------------------------- 1 | const onkeydown = () => { 2 | 3 | // 全局设置 onkeyup ,避免多次监听 这里想不到更好的方案 4 | window.onkeyup = e => { 5 | let imageModalDoms = document.querySelectorAll('.image-modal'); 6 | let imageModalMaxzIndex = localStorage.getItem('imageModalMaxzIndex'); 7 | imageModalDoms.forEach(dom => { 8 | if (dom.style.zIndex == imageModalMaxzIndex) { 9 | let leftIconDom = dom.querySelector('#left-icon'); 10 | let rightIconDom = dom.querySelector('#right-icon'); 11 | 12 | // 键盘左键 13 | if (leftIconDom && e.keyCode == 37) { 14 | leftIconDom.click(); 15 | } 16 | 17 | // 键盘右键 18 | if (rightIconDom && e.keyCode == 39) { 19 | rightIconDom.click(); 20 | } 21 | } 22 | }); 23 | }; 24 | }; 25 | 26 | export default onkeydown; 27 | -------------------------------------------------------------------------------- /src/modal_lib/readme.md: -------------------------------------------------------------------------------- 1 | usege/用法: 2 | 3 | ``` 4 | yarn add cxj-react-image 5 | // npm i cxj-react-image 6 | ``` 7 | 8 | ``` 9 | import ImageModal from 'cxj-react-image'; 10 | 11 | this.next()} {/* 控制下一张 */} 14 | prev={() => this.prev()} {/* 控制上一张 */} 15 | closeModal={() => this.closeImg()} {/* 控制modal打开关闭 */} 16 | option={{ 17 | move: true, {/* 控制拖动 */} 18 | waterMarkText: '多功能图片组件', {/* 设置水印文字 */} 19 | rotate: true, {/* 控制旋转 */} 20 | zoom: true {/* 控制放大缩小 */} 21 | }} 22 | /> 23 | ``` 24 | 25 | [github地址](https://github.com/wtfjun/cxjReactImage) 26 | 27 | [在线例子](https://wtfjun.github.io/cxjReactImage/build/) 28 | 29 | 交流请加wx: c13266836563 30 | 31 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `module.exports = { 14 | __esModule: true, 15 | default: ${assetFilename}, 16 | ReactComponent: (props) => ({ 17 | $$typeof: Symbol.for('react.element'), 18 | type: 'svg', 19 | ref: null, 20 | key: null, 21 | props: Object.assign({}, props, { 22 | children: ${assetFilename} 23 | }) 24 | }), 25 | };`; 26 | } 27 | 28 | return `module.exports = ${assetFilename};`; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/modal_lib/src/imageModal/common/onkeydown.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var onkeydown = function onkeydown() { 7 | 8 | // 全局设置 onkeyup ,避免多次监听 这里想不到更好的方案 9 | window.onkeyup = function (e) { 10 | var imageModalDoms = document.querySelectorAll('.image-modal'); 11 | var imageModalMaxzIndex = localStorage.getItem('imageModalMaxzIndex'); 12 | imageModalDoms.forEach(function (dom) { 13 | if (dom.style.zIndex == imageModalMaxzIndex) { 14 | var leftIconDom = dom.querySelector('#left-icon'); 15 | var rightIconDom = dom.querySelector('#right-icon'); 16 | 17 | // 键盘左键 18 | if (leftIconDom && e.keyCode == 37) { 19 | leftIconDom.click(); 20 | } 21 | 22 | // 键盘右键 23 | if (rightIconDom && e.keyCode == 39) { 24 | rightIconDom.click(); 25 | } 26 | } 27 | }); 28 | }; 29 | }; 30 | 31 | exports.default = onkeydown; -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--coverage') === -1 && 45 | argv.indexOf('--watchAll') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/imageModal/common/watermark.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview: 水印组件 3 | */ 4 | 5 | export default class WaterMark { 6 | constructor(container, option) { 7 | this.container = container; 8 | this.option = { 9 | width: '200px', 10 | height: '150px', 11 | opacity: .7, 12 | fillStyle: 'rgba(47, 205, 227, 0.3)', 13 | font: '20px microsoft yahei', 14 | textBaseline: 'middle', 15 | textAlign: 'center', 16 | fillText: '水印', 17 | ...option 18 | }; 19 | } 20 | 21 | draw() { 22 | const { 23 | container, 24 | option: { 25 | width, 26 | height, 27 | opacity, 28 | fillStyle, 29 | font, 30 | textBaseline, 31 | textAlign, 32 | fillText, 33 | scrollHeight 34 | } 35 | } = this; 36 | const canvas = document.createElement('canvas'); 37 | canvas.setAttribute('width', width); 38 | canvas.setAttribute('height', height); 39 | canvas.setAttribute('opacity', opacity); 40 | const ctx = canvas.getContext('2d'); 41 | 42 | ctx.textAlign = textAlign; 43 | ctx.textBaseline = textBaseline; 44 | ctx.font = font; 45 | ctx.fillStyle = fillStyle; 46 | ctx.rotate(Math.PI / 180 * 30); 47 | ctx.fillText(fillText, 80, 10); 48 | 49 | var base64Url = canvas.toDataURL(); 50 | const watermarkDiv = document.createElement('div'); 51 | watermarkDiv.setAttribute('style', ` 52 | position:absolute; 53 | top:0; 54 | left:0; 55 | width:100%; 56 | height:${scrollHeight || '100%'}; 57 | z-index:1000; 58 | pointer-events:none; 59 | background-repeat:repeat; 60 | background-image:url('${base64Url}')`); 61 | 62 | if (typeof container === 'object') { 63 | container.style.position = 'relative'; 64 | container.insertBefore(watermarkDiv, container.firstChild); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/imageModal/common/move.js: -------------------------------------------------------------------------------- 1 | // 拖拽以及控制zIndex 2 | // dv: 移动的dom 3 | // ctDv: 控制dv的dom 4 | const move = (dv, ctDv) => { 5 | // 自己控制自己,且自己移动 6 | if (!ctDv) { 7 | ctDv = dv; 8 | } 9 | 10 | let imageModalMaxzIndex = localStorage.getItem('imageModalMaxzIndex'); 11 | if (!imageModalMaxzIndex) { 12 | localStorage.setItem('imageModalMaxzIndex', 1000); 13 | dv.style.zIndex = 1000; 14 | } else { 15 | dv.style.zIndex = +imageModalMaxzIndex + 1; 16 | localStorage.setItem('imageModalMaxzIndex', dv.style.zIndex); 17 | } 18 | 19 | // 获取元素 20 | let x = 0; 21 | let y = 0; 22 | let l = 0; 23 | let t = 0; 24 | let isDown = false; 25 | const imageContentDom = dv.querySelector('.cxj-image-content'); 26 | // 鼠标按下事件 27 | ctDv.onmousedown = function(e) { 28 | 29 | imageModalMaxzIndex = localStorage.getItem('imageModalMaxzIndex'); 30 | if (dv.style.zIndex != imageModalMaxzIndex) { 31 | dv.style.zIndex = +imageModalMaxzIndex + 1; 32 | localStorage.setItem('imageModalMaxzIndex', dv.style.zIndex); 33 | } 34 | 35 | // e.preventDefault(); 36 | // 获取x坐标和y坐标 37 | x = e.clientX; 38 | y = e.clientY; 39 | 40 | // 获取左部和顶部的偏移量 41 | l = dv.offsetLeft; 42 | t = dv.offsetTop; 43 | // 开关打开 44 | isDown = true; 45 | handleMove(); 46 | }; 47 | // 鼠标移动 48 | // 再包一层是为了方便注册 避免被替换 49 | function handleMove() { 50 | onmousemove = function(e) { 51 | 52 | if (isDown == false) { 53 | return; 54 | } else { 55 | // 鼠标点击在控制dom ctDv 的时候才取消默认事件、这样html才可复制 56 | e.preventDefault(); 57 | } 58 | 59 | imageContentDom.style.opacity = .3; 60 | dv.style.border = '2px dashed #999'; 61 | // 获取x和y 62 | let nx = e.clientX; 63 | let ny = e.clientY; 64 | // 计算移动后的左偏移量和顶部的偏移量 65 | let nl = nx - (x - l); 66 | let nt = ny - (y - t); 67 | 68 | dv.style.left = nl + 'px'; 69 | dv.style.top = nt + 'px'; 70 | }; 71 | } 72 | // 鼠标抬起事件 73 | dv.onmouseup = function() { 74 | // 开关关闭 75 | isDown = false; 76 | // 取消透明 77 | imageContentDom.style.opacity = 1; 78 | dv.style.border = '2px solid transparent'; 79 | }; 80 | }; 81 | 82 | export default move; 83 | -------------------------------------------------------------------------------- /src/imageModal/index.css: -------------------------------------------------------------------------------- 1 | .cxj-image-modal { 2 | position: fixed; 3 | left: 30%; 4 | top: 40px; 5 | z-index: 1000; 6 | height: auto; 7 | transition: opacity .3s; 8 | border: 2px solid transparent; 9 | } 10 | 11 | #cxj-left-icon { 12 | z-index: 99999; 13 | position: absolute; 14 | left: 0; 15 | top: 50%; 16 | margin-top: -25px; 17 | display: block; 18 | height: 60px; 19 | line-height: 50px; 20 | font-size: 50px; 21 | cursor: pointer; 22 | color: #fff; 23 | background: #000; 24 | opacity: .3; 25 | transition: transform 0.3s; 26 | } 27 | 28 | #cxj-left-icon:hover { 29 | opacity: .5; 30 | } 31 | #cxj-right-icon { 32 | z-index: 99999; 33 | position: absolute; 34 | right: 0; 35 | top: 50%; 36 | margin-top: -25px; 37 | display: block; 38 | height: 60px; 39 | line-height: 50px; 40 | font-size: 50px; 41 | cursor: pointer; 42 | color: #fff; 43 | background: #000; 44 | opacity: .3; 45 | transition: transform 0.3s; 46 | } 47 | #cxj-right-icon:hover { 48 | opacity: .5; 49 | } 50 | 51 | 52 | .cxj-image-modal .image-item-menu { 53 | position: absolute; 54 | top: -44px; 55 | transition: all 0.3s; 56 | padding-top: 10px; 57 | width: 100%; 58 | text-align: center; 59 | font-size: 10px; 60 | } 61 | 62 | .cxj-image-modal .image-content { 63 | position: relative; 64 | overflow: hidden; 65 | } 66 | 67 | .cxj-image-modal img { 68 | transition: all 0.3s; 69 | width: 100%; 70 | } 71 | 72 | #cxj-close-icon { 73 | position: absolute; 74 | text-align: center; 75 | top: -6px; 76 | right: 12px; 77 | z-index: 1000; 78 | display: block; 79 | width: 17px; 80 | height: 50px; 81 | font-size: 30px; 82 | /* line-height: 50px; */ 83 | /* background: #000; */ 84 | /* opacity: .5; */ 85 | } 86 | 87 | #cxj-close-icon:hover { 88 | cursor: pointer; 89 | } 90 | 91 | #cxj-rotate-right, #cxj-rotate-left { 92 | width: 19px; 93 | position: absolute; 94 | right: 44px; 95 | top: 8px; 96 | z-index: 1000; 97 | /* opacity: .5; */ 98 | cursor: pointer; 99 | } 100 | 101 | #cxj-rotate-left { 102 | right: 76px; 103 | transform: rotate(180deg); 104 | top: 4px; 105 | } 106 | 107 | #cxj-rotate-right img, #cxj-rotate-left img { 108 | width: 100%; 109 | } 110 | -------------------------------------------------------------------------------- /src/modal_lib/src/imageModal/common/move.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | // 拖拽以及控制zIndex 7 | // dv: 移动的dom 8 | // ctDv: 控制dv的dom 9 | var move = function move(dv, ctDv) { 10 | // 自己控制自己,且自己移动 11 | if (!ctDv) { 12 | ctDv = dv; 13 | } 14 | 15 | var imageModalMaxzIndex = localStorage.getItem('imageModalMaxzIndex'); 16 | if (!imageModalMaxzIndex) { 17 | localStorage.setItem('imageModalMaxzIndex', 1000); 18 | dv.style.zIndex = 1000; 19 | } else { 20 | dv.style.zIndex = +imageModalMaxzIndex + 1; 21 | localStorage.setItem('imageModalMaxzIndex', dv.style.zIndex); 22 | } 23 | 24 | // 获取元素 25 | var x = 0; 26 | var y = 0; 27 | var l = 0; 28 | var t = 0; 29 | var isDown = false; 30 | var imageContentDom = dv.querySelector('.cxj-image-content'); 31 | // 鼠标按下事件 32 | ctDv.onmousedown = function (e) { 33 | 34 | imageModalMaxzIndex = localStorage.getItem('imageModalMaxzIndex'); 35 | if (dv.style.zIndex != imageModalMaxzIndex) { 36 | dv.style.zIndex = +imageModalMaxzIndex + 1; 37 | localStorage.setItem('imageModalMaxzIndex', dv.style.zIndex); 38 | } 39 | 40 | // e.preventDefault(); 41 | // 获取x坐标和y坐标 42 | x = e.clientX; 43 | y = e.clientY; 44 | 45 | // 获取左部和顶部的偏移量 46 | l = dv.offsetLeft; 47 | t = dv.offsetTop; 48 | // 开关打开 49 | isDown = true; 50 | handleMove(); 51 | }; 52 | // 鼠标移动 53 | // 再包一层是为了方便注册 避免被替换 54 | function handleMove() { 55 | onmousemove = function onmousemove(e) { 56 | 57 | if (isDown == false) { 58 | return; 59 | } else { 60 | // 鼠标点击在控制dom ctDv 的时候才取消默认事件、这样html才可复制 61 | e.preventDefault(); 62 | } 63 | 64 | imageContentDom.style.opacity = .3; 65 | dv.style.border = '2px dashed #999'; 66 | // 获取x和y 67 | var nx = e.clientX; 68 | var ny = e.clientY; 69 | // 计算移动后的左偏移量和顶部的偏移量 70 | var nl = nx - (x - l); 71 | var nt = ny - (y - t); 72 | 73 | dv.style.left = nl + 'px'; 74 | dv.style.top = nt + 'px'; 75 | }; 76 | } 77 | // 鼠标抬起事件 78 | dv.onmouseup = function () { 79 | // 开关关闭 80 | isDown = false; 81 | // 取消透明 82 | imageContentDom.style.opacity = 1; 83 | dv.style.border = '2px solid transparent'; 84 | }; 85 | }; 86 | 87 | exports.default = move; -------------------------------------------------------------------------------- /src/modal_lib/src/imageModal/index.css: -------------------------------------------------------------------------------- 1 | .cxj-image-modal { 2 | position: fixed; 3 | left: 30%; 4 | top: 40px; 5 | z-index: 1000; 6 | height: auto; 7 | transition: opacity .3s; 8 | border: 2px solid transparent; 9 | } 10 | 11 | #cxj-left-icon { 12 | z-index: 99999; 13 | position: absolute; 14 | left: 0; 15 | top: 50%; 16 | margin-top: -25px; 17 | display: block; 18 | height: 60px; 19 | line-height: 50px; 20 | font-size: 50px; 21 | cursor: pointer; 22 | color: #fff; 23 | background: #000; 24 | opacity: .3; 25 | transition: transform 0.3s; 26 | } 27 | 28 | #cxj-left-icon:hover { 29 | opacity: .5; 30 | } 31 | #cxj-right-icon { 32 | z-index: 99999; 33 | position: absolute; 34 | right: 0; 35 | top: 50%; 36 | margin-top: -25px; 37 | display: block; 38 | height: 60px; 39 | line-height: 50px; 40 | font-size: 50px; 41 | cursor: pointer; 42 | color: #fff; 43 | background: #000; 44 | opacity: .3; 45 | transition: transform 0.3s; 46 | } 47 | #cxj-right-icon:hover { 48 | opacity: .5; 49 | } 50 | 51 | 52 | .cxj-image-modal .image-item-menu { 53 | position: absolute; 54 | top: -44px; 55 | transition: all 0.3s; 56 | padding-top: 10px; 57 | width: 100%; 58 | text-align: center; 59 | font-size: 10px; 60 | } 61 | 62 | .cxj-image-modal .image-content { 63 | position: relative; 64 | overflow: hidden; 65 | } 66 | 67 | .cxj-image-modal img { 68 | transition: all 0.3s; 69 | width: 100%; 70 | } 71 | 72 | #cxj-close-icon { 73 | position: absolute; 74 | text-align: center; 75 | top: -6px; 76 | right: 12px; 77 | z-index: 1000; 78 | display: block; 79 | width: 17px; 80 | height: 50px; 81 | font-size: 30px; 82 | /* line-height: 50px; */ 83 | /* background: #000; */ 84 | /* opacity: .5; */ 85 | } 86 | 87 | #cxj-close-icon:hover { 88 | cursor: pointer; 89 | } 90 | 91 | #cxj-rotate-right, #cxj-rotate-left { 92 | width: 19px; 93 | position: absolute; 94 | right: 44px; 95 | top: 8px; 96 | z-index: 1000; 97 | /* opacity: .5; */ 98 | cursor: pointer; 99 | } 100 | 101 | #cxj-rotate-left { 102 | right: 76px; 103 | transform: rotate(180deg); 104 | top: 4px; 105 | } 106 | 107 | #cxj-rotate-right img, #cxj-rotate-left img { 108 | width: 100%; 109 | } 110 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Container.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | // import ImageModal from 'cxj-react-image'; 3 | import ImageModal from './imageModal'; 4 | 5 | import './index.css'; 6 | 7 | class Container extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | imageList: [], 12 | currentImageIndex: 0, 13 | modalVisible: false, 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | // 可以在这里异步获取图片数组 19 | this.setState({ 20 | imageList: [ 21 | 'http://pic39.photophoto.cn/20160630/1155115644653376_b.jpg', 22 | 'http://image.biaobaiju.com/uploads/20180801/23/1533136618-ARPmGQwjvD.jpg', 23 | 'http://img18.3lian.com/d/file/201712/08/8c2b94d5ec0bf634e3881b4ad5568b57.png', 24 | 'http://img3.duitang.com/uploads/blog/201509/15/20150915125228_ixEts.jpeg', 25 | 'https://s10.sinaimg.cn/mw690/001whaJHzy7kChbjZRn59&690', 26 | 'https://upload-images.jianshu.io/upload_images/5691297-648aed056c02698f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/542/format/webp' 27 | ] 28 | }) 29 | } 30 | 31 | prev() { 32 | const { currentImageIndex } = this.state; 33 | if (currentImageIndex > 0) { 34 | this.setState({ currentImageIndex: currentImageIndex - 1 }); 35 | } 36 | } 37 | 38 | next() { 39 | const { currentImageIndex, imageList } = this.state; 40 | if (currentImageIndex < imageList.length - 1) { 41 | this.setState({ currentImageIndex: currentImageIndex + 1 }); 42 | } 43 | } 44 | 45 | handleImgClick = index => { 46 | this.setState({ 47 | modalVisible: true, 48 | currentImageIndex: index 49 | }); 50 | 51 | } 52 | 53 | closeImg = () => this.setState({ modalVisible: false }); 54 | 55 | render() { 56 | const { modalVisible, imageList, currentImageIndex } = this.state; 57 | return ( 58 |
59 |
    60 | {imageList.map((src, i) =>
  • 61 | this.handleImgClick(i) } 67 | /> 68 |
  • )} 69 | 70 |
71 | 72 | {modalVisible && this.next()} 75 | prev={() => this.prev()} 76 | closeModal={() => this.closeImg()} 77 | option={{ 78 | move: true, 79 | waterMarkText: '多功能图片组件', 80 | rotate: true, 81 | zoom: true 82 | }} 83 | />} 84 |
85 | ); 86 | } 87 | } 88 | 89 | export default Container; 90 | -------------------------------------------------------------------------------- /src/imageModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Move from './common/move'; 3 | import Zoom from './common/zoom'; 4 | import WaterMark from './common/watermark'; 5 | 6 | import RotateIcon from './images/rotate.png'; 7 | import CloseIcon from './images/close.png'; 8 | 9 | import './index.css'; 10 | class ImageModal extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | imgRotate: 0 15 | } 16 | } 17 | 18 | componentDidMount() { 19 | const { 20 | option: { 21 | move, waterMarkText 22 | } 23 | } = this.props; 24 | let imageModalDom = this.refs.imageModal; 25 | let imageContentDom = imageModalDom.querySelector('.cxj-image-content'); 26 | 27 | // 注册拖拽 28 | move && Move(imageModalDom); 29 | 30 | // 画水印 部分图片不画水印,比如聊天图片 31 | if (waterMarkText) { 32 | new WaterMark(imageContentDom, { fillText: waterMarkText }).draw(); 33 | } 34 | } 35 | 36 | componentWillReceiveProps(nextProps) { 37 | const { 38 | src 39 | } = this.props; 40 | if (src !== nextProps.src) { 41 | this.setState({ imgRotate: 0 }); 42 | } 43 | } 44 | 45 | handleRotateRight = () => { 46 | this.setState({ imgRotate: this.state.imgRotate + 90 }); 47 | } 48 | 49 | handleRotateLeft = () => { 50 | this.setState({ imgRotate: this.state.imgRotate - 90 }) 51 | } 52 | 53 | render() { 54 | let { src, prev, next, closeModal, option: { rotate, zoom } } = this.props; 55 | let { imgRotate } = this.state; 56 | return ( 57 |
58 |
zoom && Zoom(e, this.refs.imageModal)} 63 | onContextMenu={e => { 64 | e.preventDefault(); 65 | +e.button === 2 && closeModal();} 66 | } 67 | > 68 | {rotate && 69 | 70 | } 71 | {rotate && 72 | 73 | } 74 | 75 | 76 | 77 | {prev && <} 78 | {next && >} 79 |
80 | 81 |
82 |
83 |
84 | ); 85 | } 86 | } 87 | 88 | export default ImageModal; 89 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right