我正在尝试修改画布图案的来源,但无法达到我想要的效果。
我需要绘制一条填充点状图案的线条。点状图案是通过createPattern
创建的(为动态创建的画布元素提供)。
画布(基本上是一个红点)的创建方式如下:
function getPatternCanvas() {
var dotWidth = 20,
dotDistance = 5,
patternCanvas = document.createElement('canvas'),
patternCtx = patternCanvas.getContext('2d');
patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;
// attempt #1:
// patternCtx.translate(10, 10);
patternCtx.fillStyle = 'red';
patternCtx.beginPath();
patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);
patternCtx.closePath();
patternCtx.fill();
return patternCanvas;
}
然后使用该模式(画布)绘制一条线:
var canvasEl = document.getElementById('c');
var ctx = canvasEl.getContext('2d');
var pattern = ctx.createPattern(getPatternCanvas(), 'repeat');
// attempt #2
// ctx.translate(10, 10);
ctx.strokeStyle = pattern;
ctx.lineWidth = 30;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 100);
ctx.stroke();
所以我们得到了这个:
现在,我想用10px来抵消这些点的来源。翻译模式画布没有用,因为我们没有得到完整的点:
翻译画布本身的上下文并没有帮助,因为它抵消了线条,而不是模式本身:
翻译上下文似乎不会影响模式原点。
有没有办法修改模式本身的偏移?
答案 0 :(得分:10)
更新由于此答案已发布,现在(自2015/02起)setTransform()
个实例本身就有CanvasPattern
(请参阅specs)。它可能还没有在所有浏览器中都可用(只有Firefox在编写时支持它)。
您可以偏移主画布并将增量值添加到该行的实际位置:
var offsetX = 10, offsetY = 10;
ctx.translate(offsetX, offsetY);
ctx.lineTo(x - offsetX, y - offsetY);
// ...
<强> Example 强>
(演示只显示正在翻译的模式,但当然,通常你会将它与它一起移动)。
等。这样您就可以取消该行本身的翻译。但它引入了一些开销,因为每次都需要计算坐标,除非你可以缓存结果值。
我能想到的另一种方式是创造一种自我模式。即对于图案画布重复点,这样当你将它移到边界外时,它会以相反的方向重复。
例如,这里第一个正方形是正常模式,第二个是描述为方法二的偏移模式,第三个图像使用偏移模式进行填充,显示它将起作用。
关键是这两个模式是相同大小,并且第一个模式重复偏移到第二个版本中。然后可以将第二个版本用作主要的填充。
<强> Example 2 强>
<强> Example 3 animated 强>
var ctx = demo.getContext('2d'),
pattern;
// create the pattern
ctx.fillStyle = 'red';
ctx.arc(25, 25, 22, 0, 2*Math.PI);
ctx.fill();
// offset and repeat first pattern to base for second pattern
ctx = demo2.getContext('2d');
pattern = ctx.createPattern(demo, 'repeat');
ctx.translate(25, 25);
ctx.fillStyle = pattern;
ctx.fillRect(-25, -25, 50, 50);
// use second pattern to fill main canvas
ctx = demo3.getContext('2d');
pattern = ctx.createPattern(demo2, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 200, 200);
答案 1 :(得分:6)
您可以在绘制线条/形状后简单地翻译上下文。在抚摸/填充之前抵消模式。更新了小提琴http://jsfiddle.net/28BSH/27/
android.support.v7.widget.SearchView
android-support-v7-appcompat_23.1
答案 2 :(得分:0)
我碰巧在codepen上创建了这个小工具,可能有助于创建填充模式。您可以应用多个图案层,创建线条,圆形和正方形,或应用颜色。
// Utils
const produce = immer.default
// Enums
const fillPatternType = {
LINE: 'line',
CIRCLE: 'circle',
SQUARE: 'square'
}
// Utils
function rgbToHex(rgb) {
const [r, g, b] = rgb
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
] : null;
}
const DEFAULT_FILL_PATTERN_ROTATION = 0
const DEFAULT_FILL_PATTERN_THICKNESS = 1
const DEFAULT_FILL_PATTERN_SIZE = 20
const DEFAULT_FILL_PATTERN_BACKGROUND_COLOR = '#ffffff'
class Application extends React.Component {
constructor() {
super()
// Initialize state
this.state = {
stages: [{
patternName: 'pattern 1',
isFocus: true
}],
patternMap: {
'pattern 1': {
size: DEFAULT_FILL_PATTERN_SIZE,
backgroundColor: '#ffffff',
contents: []
}
}
}
// Binding callbacks
this.addNewStage = this.addNewStage.bind(this)
this.getFillPatternCanvasesByName = this.getFillPatternCanvasesByName.bind(this)
this.getFillPatternConfigsByName = this.getFillPatternConfigsByName.bind(this)
this.getFillPatternCanvasesByConfigs = this.getFillPatternCanvasesByConfigs.bind(this)
this.addPatternControl = this.addPatternControl.bind(this)
this.toggleControl = this.toggleControl.bind(this)
this.removeControl = this.removeControl.bind(this)
this.setPatternConfig = this.setPatternConfig.bind(this)
this.focusCanvasStage = this.focusCanvasStage.bind(this)
this.setPatternSize = this.setPatternSize.bind(this)
this.setPatternForegroundColor = this.setPatternForegroundColor.bind(this)
this.setPatternBackgroundColor = this.setPatternBackgroundColor.bind(this)
this.togglePatternColor = this.togglePatternColor.bind(this)
this.downloadStages = this.downloadStages.bind(this)
}
// Add a new canvas stage
addNewStage(evt) {
const patternName = `pattern ${Object.keys(this.state.patternMap).length + 1}`
const focusedPatternName = this.getFocusedPatternName()
let newStage = {
patternName,
isFocus: true
}
// get an updated existing state
this.setState(produce(this.state, (draftState) => {
for (let stage of draftState.stages) {
stage.isFocus = false
}
draftState.patternMap[patternName] = this.getFillPatternConfigsByName(focusedPatternName)
draftState.stages.push(newStage)
}))
}
downloadStages() {
let zip = new JSZip()
const stageCanvases = document.querySelectorAll('canvas.stage')
const folder = zip.folder('stagePngs')
Array.prototype.slice.call(stageCanvases).forEach((stageCanvas, i) => {
// Using blob is better but it requires promise.all
// For simplicity here we are just going to decode base64 string
// stageCanvas.toBlob((blob => {
// folder.file(`pattern_${i + 1}.png`, blob)
// }))
let imgData = stageCanvas.toDataURL()
imgData = imgData.substr(22)
imgData = atob(imgData)
folder.file(`pattern_${i + 1}.png`, imgData, {
// This is needed for jszip
// See https://stackoverflow.com/questions/37557426/put-generated-png-image-into-jszip
binary: true
})
// Put the pattern config in folder as well
folder.file(`pattern_${i + 1}.json`, JSON.stringify(this.state.patternMap[`pattern ${i + 1}`], null, 2))
})
folder.generateAsync({
type: 'base64'
})
.then((base64) => {
window.location = "data:application/zip;base64," + base64
})
}
removeNewStage(evt) {
}
getFillPatternConfigsByName(patternName) {
return this.state.patternMap[patternName]
}
getFillPatternCanvasesByName(patternName) {
const fillPatternConfigs = this.getFillPatternConfigsByName(patternName)
return this.getFillPatternCanvasesByConfigs(fillPatternConfigs)
}
getFillPatternCanvasesByConfigs(fillPatternConfigs) {
return fillPatternConfigs.contents.map((fillPatternConfig) => {
const size = fillPatternConfigs.size || DEFAULT_FILL_PATTERN_SIZE
const backgroundColor = fillPatternConfigs.backgroundColor || DEFAULT_FILL_PATTERN_BACKGROUND_COLOR
switch(fillPatternConfig.type) {
case fillPatternType.LINE:
return this.getLineFillPatternCanvases(Object.assign({}, fillPatternConfig, {
size,
backgroundColor
}))
case fillPatternType.CIRCLE:
return this.getCircleFillPatternCanvases(Object.assign({}, fillPatternConfig, {
size,
backgroundColor
}))
case fillPatternType.SQUARE:
return this.getSquareFillPatternCanvases(Object.assign({}, fillPatternConfig, {
size,
backgroundColor
}))
default:
return this.getLineFillPatternCanvases(Object.assign({}, fillPatternConfig, {
size,
backgroundColor
}))
}
})
}
getLineFillPatternCanvases(fillPatternConfig) {
let {
size,
density,
thickness,
rotation,
offsetX,
offsetY,
foregroundColor
} = fillPatternConfig
rotation = rotation / 360 * Math.PI * 2
let canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
let textureCtx = canvas.getContext('2d')
// Rotate texture canvas
textureCtx.translate(size / 2, size / 2)
textureCtx.rotate(rotation)
textureCtx.translate(-size / 2, -size / 2)
let minY = -size * 1.3
let maxY = size * 2.3
let minX = -size * 1.3
let maxX = size * 2.3
let y = minY
textureCtx.strokeStyle = foregroundColor
while (y < maxY) {
textureCtx.beginPath();
textureCtx.lineWidth = thickness;
textureCtx.moveTo(minX + offsetX, y + offsetY);
textureCtx.lineTo(maxX + offsetX, y + offsetY);
textureCtx.stroke();
y += density;
}
return canvas
}
getCircleFillPatternCanvases(fillPatternConfig) {
let {
size,
density,
thickness,
rotation,
offsetX,
offsetY,
foregroundColor
} = fillPatternConfig
rotation = rotation / 360 * Math.PI * 2
let canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
let textureCtx = canvas.getContext('2d')
// Rotate texture canvas
textureCtx.translate(size / 2, size / 2)
textureCtx.rotate(rotation)
textureCtx.translate(-size / 2, -size / 2)
let minY = -size * 1.3
let maxY = size * 2.3
let minX = -size * 1.3
let maxX = size * 2.3
let x
let y
textureCtx.fillStyle = foregroundColor
for (y = minY; y < maxY; y += density) {
for (x = minX; x < maxX; x += density) {
textureCtx.beginPath();
textureCtx.arc(x + offsetX, y + offsetY, thickness, 0, Math.PI * 2);
textureCtx.fill();
}
}
return canvas
}
getSquareFillPatternCanvases(fillPatternConfig) {
let {
size,
density,
thickness,
rotation,
offsetX,
offsetY,
foregroundColor
} = fillPatternConfig
rotation = rotation / 360 * Math.PI * 2
let canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
let textureCtx = canvas.getContext('2d')
// Rotate texture canvas
textureCtx.translate(size / 2, size / 2)
textureCtx.rotate(rotation)
textureCtx.translate(-size / 2, -size / 2)
let minY = -size * 1.3
let maxY = size * 2.3
let minX = -size * 1.3
let maxX = size * 2.3
let x
let y
textureCtx.fillStyle = foregroundColor
for (y = minY; y < maxY; y += density) {
for (x = minX; x < maxX; x += density) {
textureCtx.beginPath();
textureCtx.rect(x + offsetX, y + offsetY, thickness, thickness);
textureCtx.fill();
}
}
return canvas
}
getFocusedPatternName() {
return this.state.stages.filter(stage => stage.isFocus)[0].patternName
}
addPatternControl(patternName, patternControl) {
this.setState(produce(this.state, (draftState) => {
draftState.patternMap[patternName].contents.unshift(Object.assign({}, {
density: 5,
thickness: 1,
rotation: 0,
offsetX: 0,
offsetY: 0,
foregroundColor: '#000000'
}, patternControl))
}))
}
toggleControl(patternName, patternConfigIndex) {
this.setState(produce(this.state, (draftState) => {
draftState.patternMap[patternName].contents[patternConfigIndex].showDetails = !draftState.patternMap[patternName].contents[patternConfigIndex].showDetails
}))
}
removeControl(patternName, patternConfigIndex) {
this.setState(produce(this.state, (draftState) => {
draftState.patternMap[patternName].contents.splice(patternConfigIndex, 1)
}))
}
setPatternConfig(patternName, patternConfigIndex, changeset) {
this.setState(produce(this.state, (draftState) => {
Object.assign(draftState.patternMap[patternName].contents[patternConfigIndex], changeset)
}))
}
setPatternSize(patternName, patternSize) {
this.setState(produce(this.state, (draftState) => {
draftState.patternMap[patternName].size = patternSize
}))
}
setPatternForegroundColor(patternName, patternConfigIndex, color) {
this.setState(produce(this.state, (draftState) => {
draftState.patternMap[patternName].contents[patternConfigIndex].foregroundColor = color
}))
}
setPatternBackgroundColor(patternName, color) {
this.setState(produce(this.state, (draftState) => {
draftState.patternMap[patternName].backgroundColor = color
}))
}
togglePatternColor(patternName, patternConfigIndex) {
this.setState(produce(this.state, (draftState) => {
let foregroundColor = draftState.patternMap[patternName].contents[patternConfigIndex].foregroundColor
draftState.patternMap[patternName].contents[patternConfigIndex].foregroundColor = draftState.patternMap[patternName].backgroundColor
draftState.patternMap[patternName].backgroundColor = foregroundColor
}))
}
focusCanvasStage(stageIndex) {
this.setState(produce(this.state, (draftState) => {
for (let stage of draftState.stages) {
stage.isFocus = false
}
draftState.stages[stageIndex].isFocus = true
}))
}
render() {
return (
<div>
<CanvasStageContainer stages={this.state.stages}
addNewStageCallback={this.addNewStage}
downloadStages={this.downloadStages}
getFillPatternCanvasesByName={this.getFillPatternCanvasesByName}
getFillPatternConfigsByName={this.getFillPatternConfigsByName}
focusCanvasStage={this.focusCanvasStage}></CanvasStageContainer>
<hr />
<PatternContainer focusPatternName={this.getFocusedPatternName()}
getFillPatternCanvasesByName={this.getFillPatternCanvasesByName}
getFillPatternConfigsByName={this.getFillPatternConfigsByName}
getFillPatternCanvasesByConfigs={this.getFillPatternCanvasesByConfigs}
addPatternControl={this.addPatternControl}
toggleControl={this.toggleControl}
removeControl={this.removeControl}
setPatternConfig={this.setPatternConfig}
setPatternSize={this.setPatternSize}
setPatternForegroundColor={this.setPatternForegroundColor}
setPatternBackgroundColor={this.setPatternBackgroundColor}
togglePatternColor={this.togglePatternColor}></PatternContainer>
</div>
)
}
}
// CanvasStageContainer component
const CanvasStageContainer = (props) => {
let stages = props.stages.map((stage, i) => (
<CanvasStage key={i}
patternName={stage.patternName}
isFocus={stage.isFocus}
getFillPatternCanvasesByName={props.getFillPatternCanvasesByName}
getFillPatternConfigsByName={props.getFillPatternConfigsByName}
focusCanvasStage={() => props.focusCanvasStage(i)}></CanvasStage>
))
return (
<div className="stage-container">
<div className="stage-wrapper">
{stages}
</div>
<button className="btn new-target"
onClick={props.addNewStageCallback}>Add new stage</button>
<button className="btn"
onClick={props.downloadStages}>Download stages</button>
</div>
)
}
// CanvasStage component
// Need to use stateful component as we need to get ref to canvas
const DEFAULT_CANVAS_WIDTH = 150
const DEFAULT_CANVAS_HEIGHT = 150
class CanvasStage extends React.Component {
componentDidMount() {
this.updateCanvas();
}
componentDidUpdate() {
this.updateCanvas();
}
updateCanvas() {
const canvas = this.refs.canvas
const context = canvas.getContext('2d')
const patternName = this.props.patternName
const fillPatternCanvases = this.props.getFillPatternCanvasesByName(patternName)
const fillPatternConfigs = this.props.getFillPatternConfigsByName(patternName)
canvas.width = DEFAULT_CANVAS_WIDTH
canvas.height = DEFAULT_CANVAS_HEIGHT
context.clearRect(0, 0, canvas.width, canvas.height)
context.fillStyle = fillPatternConfigs.backgroundColor
context.fillRect(0, 0, canvas.width, canvas.height)
for (let fillPatternCanvas of fillPatternCanvases) {
context.fillStyle = context.createPattern(fillPatternCanvas, 'repeat')
context.fillRect(0, 0, canvas.width, canvas.height)
}
}
render() {
return (
<div className='canvas-stage'>
<canvas width={this.props.width || DEFAULT_CANVAS_WIDTH}
height={this.props.height || DEFAULT_CANVAS_HEIGHT}
className="stage main"
onClick={this.props.focusCanvasStage}
ref="canvas"></canvas>
<span>{this.props.patternName}</span>
</div>
)
}
}
const PatternContainer = (props) => {
return (
<div className="pattern-container">
<PatternPanel patternName={props.focusPatternName} {...props}></PatternPanel>
</div>
)
}
class PatternPanel extends React.Component {
componentDidMount() {
this.updateCanvas();
}
componentDidUpdate() {
this.updateCanvas();
}
updateCanvas() {
const canvas = this.refs.patternCanvas
const context = canvas.getContext('2d')
const patternName = this.props.patternName
const patternConfigs = this.props.getFillPatternConfigsByName(patternName)
const fillPatternCanvases = this.props.getFillPatternCanvasesByConfigs(patternConfigs)
canvas.width = patternConfigs.size
canvas.height = patternConfigs.size
context.clearRect(0, 0, canvas.width, canvas.height)
for (let fillPatternCanvas of fillPatternCanvases) {
context.drawImage(fillPatternCanvas, 0, 0, canvas.width, canvas.height)
}
}
render() {
const patternName = this.props.patternName
const patternConfigs = this.props.getFillPatternConfigsByName(patternName)
const patternControls = patternConfigs.contents.map((patternConfig, i) => (
<PatternControl key={i}
{...patternConfig}
patternSize={patternConfigs.size}
backgroundColor={patternConfigs.backgroundColor}
toggleControl={() => this.props.toggleControl(patternName, i)}
removeControl={() => this.props.removeControl(patternName, i)}
setPatternConfig={(changeSet) => this.props.setPatternConfig(patternName, i, changeSet)}
setPatternSize={(size) => this.props.setPatternSize(patternName, size)}
setPatternForegroundColor={(color) => this.props.setPatternForegroundColor(patternName, i, color)}
setPatternBackgroundColor={(color) => this.props.setPatternBackgroundColor(patternName, color)}
togglePatternColor={() => this.props.togglePatternColor(patternName, i)}></PatternControl>
))
return (
<div className='pattern-panel'>
<div className='pattern-canvas-wrapper'>
<strong>{patternName}</strong>
<canvas className='pattern-canvas'
ref='patternCanvas'></canvas>
</div>
<div className='pattern-panel-control-wrapper'>
<button className='pattern-panel-add-control'
onClick={() => this.props.addPatternControl(patternName, {
type: fillPatternType.LINE
})}>Add line</button>
<button className='pattern-panel-add-control'
onClick={() => this.props.addPatternControl(patternName, {
type: fillPatternType.CIRCLE
})}>Add circle</button>
<button className='pattern-panel-add-control'
onClick={() => this.props.addPatternControl(patternName, {
type: fillPatternType.SQUARE
})}>Add square</button>
<ul className='pattern-controls'>
{patternControls}
</ul>
</div>
</div>
)
}
}
// Pattern control component
const PatternControl = (props) => {
return (
<div className='pattern-control'>
<div className='pattern-control-summary'>
<span className='pattern-control-summary-title'>{props.type}</span>
<div className='pattern-control-items'
onClick={props.toggleControl}>
<div className='pattern-control-item'>
<strong>size</strong>
<span>{props.patternSize} px</span>
</div>
<div className='pattern-control-item'>
<strong>density</strong>
<span>{props.density} px</span>
</div>
<div className='pattern-control-item'>
<strong>thickness</strong>
<span>{props.thickness} px</span>
</div>
<div className='pattern-control-item'>
<strong>rotation</strong>
<span>{props.rotation} degree</span>
</div>
<div className='pattern-control-item'>
<strong>X offset</strong>
<span>{props.offsetX} px</span>
</div>
<div className='pattern-control-item'>
<strong>Y offset</strong>
<span>{props.offsetY} px</span>
</div>
<div className='pattern-control-item'>
<strong>foreground color</strong>
<span>{props.foregroundColor}</span>
</div>
<div className='pattern-control-item'>
<strong>background color</strong>
<span>{props.backgroundColor}</span>
</div>
</div>
<div className='pattern-control-buttons'>
<button onClick={props.togglePatternColor}>Toggle color</button>
<button onClick={props.removeControl}>Remove</button>
</div>
</div>
{props.showDetails ? (
<div className='pattern-control-body'>
<div className='pattern-control-row'>
<span>size ({props.patternSize} px)</span>
<input type='range' min='16' max='64' step='4' value={props.patternSize}
onChange={(evt) => props.setPatternSize(parseInt(evt.target.value))}></input>
</div>
<div className='pattern-control-row'>
<span>density ({props.density} px)</span>
<input type='range' min='1' max='20' step='1' value={props.density}
onChange={(evt) => props.setPatternConfig({
density: parseInt(evt.target.value)
})}></input>
</div>
<div className='pattern-control-row'>
<span>thickness ({props.thickness} px)</span>
<input type='range' min='1' max='10' step='1' value={props.thickness}
onChange={(evt) => props.setPatternConfig({
thickness: parseInt(evt.target.value)
})}></input>
</div>
<div className='pattern-control-row'>
<span>rotation ({props.rotation} px)</span>
<input type='range' min='0' max='360' step='1' value={props.rotation}
onChange={(evt) => props.setPatternConfig({
rotation: parseInt(evt.target.value)
})}></input>
</div>
<div className='pattern-control-row'>
<span>X offset ({props.offsetX} px)</span>
<input type='range' min='0' max={props.patternSize} step='1' value={props.offsetX}
onChange={(evt) => props.setPatternConfig({
offsetX: parseInt(evt.target.value)
})}></input>
</div>
<div className='pattern-control-row'>
<span>Y offset ({props.offsetY} px)</span>
<input type='range' min='0' max={props.patternSize} step='1' value={props.offsetY}
onChange={(evt) => props.setPatternConfig({
offsetY: parseInt(evt.target.value)
})}></input>
</div>
<div className='pattern-control-row'>
<span>foreground color RED ({hexToRgb(props.foregroundColor)[0]})</span>
<input type='range' min='0' max='255' step='1' value={hexToRgb(props.foregroundColor)[0]}
onChange={(evt) => {
props.setPatternForegroundColor(
rgbToHex([parseInt(evt.target.value)].concat(hexToRgb(props.foregroundColor).slice(1))))
}}></input>
</div>
<div className='pattern-control-row'>
<span>foreground color GREEN ({hexToRgb(props.foregroundColor)[1]})</span>
<input type='range' min='0' max='255' step='1' value={hexToRgb(props.foregroundColor)[1]}
onChange={(evt) => {
let rgb = hexToRgb(props.foregroundColor)
props.setPatternForegroundColor(
rgbToHex([rgb[0], parseInt(evt.target.value), rgb[2]]))
}}></input>
</div>
<div className='pattern-control-row'>
<span>foreground color BLUE ({hexToRgb(props.foregroundColor)[2]})</span>
<input type='range' min='0' max='255' step='1' value={hexToRgb(props.foregroundColor)[2]}
onChange={(evt) => {
props.setPatternForegroundColor(
rgbToHex(hexToRgb(props.foregroundColor).slice(0, 2).concat([parseInt(evt.target.value)])))
}}></input>
</div>
</div>
) : null}
</div>
)
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
&#13;
html,
body {
height: 100%;
font-family: sans-serif;
}
button {
cursor: pointer;
}
ul {
padding: 0;
}
#app {
min-width: 1100px;
}
canvas.stage {
border: 1px solid #c1c1c1;
cursor: pointer;
}
canvas.pattern-canvas {
border: 1px solid #c1c1c1;
margin: 20px;
}
button.btn {
height: 25px;
background-color: white;
margin-right: 10px;
}
.stage-container {
padding: 10px;
}
.stage-wrapper {
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
}
.canvas-stage {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 10px;
margin-bottom: 10px;
}
.pattern-container {
padding: 10px;
}
.pattern-panel {
display: flex;
border: 1px solid #c1c1c1;
padding: 10px;
}
.pattern-panel .pattern-canvas-wrapper {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20px;
}
.pattern-panel .pattern-panel-control-wrapper {
flex-grow: 1;
}
.pattern-panel .pattern-panel-add-control {
margin-right: 10px;
}
.pattern-control {
border: 1px solid #c1c1c1;
margin-top: 10px;
}
.pattern-control .pattern-control-summary {
display: flex;
align-items: center;
height: 40px;
border-bottom: 1px solid #c1c1c1;
}
.pattern-control .pattern-control-summary .pattern-control-summary-title {
height: 100%;
box-sizing: border-box;
padding: 10px;
border-right: 1px solid #c1c1c1;
flex-shrink: 0;
}
.pattern-control .pattern-control-summary .pattern-control-buttons {
display: flex;
align-items: center;
flex-shrink: 0;
height: 100%;
border-left: 1px solid #c1c1c1;
}
.pattern-control .pattern-control-summary .pattern-control-buttons button {
margin: 5px;
}
.pattern-control .pattern-control-summary .pattern-control-items {
display: flex;
justify-content: space-around;
flex-grow: 1;
cursor: pointer;
}
.pattern-control .pattern-control-summary .pattern-control-item {
display: flex;
flex-direction: column;
align-items: center;
}
.pattern-control .pattern-control-summary .pattern-control-item strong {
font-weight: bold;
}
.pattern-control .pattern-control-row {
display: flex;
align-items: center;
justify-content: space-around;
margin: 5px;
}
.pattern-control .pattern-control-row span {
width: 25%;
text-align: right;
}
.pattern-control .pattern-control-row input {
flex-grow: 1;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/immer/dist/immer.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.js"></script>
<div id="app"></app>
&#13;