这是基于此 codepen的解决方案。
文件resizableColumns.js:
$(document).ready(function() {
(function($, window, document, undefined) {
$.widget('ce.resizableGrid', {
_create: function() {
this.resizing = false;
this._on({
'mousedown .resizable-column-handle': '_resizeStartHandler',
'mousemove': '_resizeHandler',
'mouseup': '_resizeStopHandler',
'mouseleave': '_resizeStopHandler'
});
},
_init: function() {
this._createHelpers();
},
_createHelpers: function() {
this.element.addClass('resizable-grid');
this.element.find('> .row:not(.resizable-row)').each(function(rowIndex, rowElement) {
var row = $(rowElement);
row.addClass('resizable-row');
row.find('> [class^="col-"]:not(.resizable-column)').each(function(columnIndex, columnElement) {
var column = $(columnElement);
column.addClass('resizable-column');
column.append(
$('<div>', {
class: 'resizable-column-handle resizable-column-handle-w',
'data-is-west': 'true'
}),
$('<div>', {
class: 'resizable-column-handle resizable-column-handle-e',
'data-is-west': 'false'
})
);
});
});
},
_resizeStartHandler: function(event) {
this.resizing = {};
this.resizing.handle = $(event.currentTarget).addClass('resizable-column-handle-resizing');
this.resizing.column = this.resizing.handle.closest('.resizable-column').addClass('resizable-column-resizing');
this.resizing.row = this.resizing.column.closest('.resizable-row').addClass('resizable-row-resizing');
this.resizing.handleIsWest = this.resizing.handle.data('isWest');
this.resizing.directionIsWest = this._getResizingDirectionIsWest(event.pageX);
this.resizing.columnSize = this._getColumnSize(this.resizing.column);
this.resizing.siblings = this._getResizingSiblings(this.resizing.column);
this.resizing.offsets = this._getResizingOffsets();
this.element.addClass('resizable-grid-resizing');
},
_resizeHandler: function(event) {
if(!this.resizing) {
return;
}
this.resizing.directionIsWest = this._getResizingDirectionIsWest(event.pageX);
var resizingOffsetSize = this._getResizingOffsetSize(event.pageX);
if(resizingOffsetSize && (this.resizing.columnSize !== resizingOffsetSize)) {
if(resizingOffsetSize > this.resizing.columnSize) {
var widestColumn = this._getWidestColumn(this.resizing.siblings),
widestColumnSize = this._getColumnSize(widestColumn);
this._setColumnSize(widestColumn, (widestColumnSize - 1));
this._setColumnSize(this.resizing.column, resizingOffsetSize);
} else {
var narrowestColumn = this._getNarrowestColumn(this.resizing.siblings),
narrowestColumnSize = this._getColumnSize(narrowestColumn);
this._setColumnSize(narrowestColumn, (narrowestColumnSize + 1));
this._setColumnSize(this.resizing.column, resizingOffsetSize);
}
this.resizing.columnSize = resizingOffsetSize;
}
},
_resizeStopHandler: function(event) {
if(!this.resizing) {
return;
}
this.resizing.handle.removeClass('resizable-column-handle-resizing');
this.resizing.column.removeClass('resizable-column-resizing');
this.resizing.row.removeClass('resizable-row-resizing');
this.element.removeClass('resizable-grid-resizing');
this.resizing = false;
},
_getResizingDirectionIsWest: function(x) {
var resizingDirectionIsWest;
if(!this.resizing.directionLastX) {
this.resizing.directionLastX = x;
resizingDirectionIsWest = null;
} else {
if(x < this.resizing.directionLastX) {
resizingDirectionIsWest = true;
} else {
resizingDirectionIsWest = false;
}
this.resizing.directionLastX = x;
}
return resizingDirectionIsWest;
},
_getResizingSiblings: function(column) {
return ((this.resizing.handleIsWest) ? column.prevAll() : column.nextAll());
},
_getResizingOffsetSize: function(x) {
var that = this,
resizingOffsetSize;
$.each(this.resizing.offsets, function(index, offset) {
if((that.resizing.directionIsWest && ((x <= offset.end) && (x >= offset.start))) || (!that.resizing.directionIsWest && ((x >= offset.start) && (x <= offset.end)))) {
resizingOffsetSize = offset.size;
}
});
return resizingOffsetSize;
},
_getResizingOffsets: function() {
var that = this,
row = this.resizing.row.clone(),
css = {
'height': '1px',
'min-height': '1px',
'max-height': '1px'
};
row.removeClass('resizable-row resizable-row-resizing').css(css);
row.children().empty().removeClass('resizable-column resizable-column-resizing').css(css);
this.resizing.row.parent().append(row);
var column = row.children().eq(this.resizing.row.children().index(this.resizing.column)),
totalSize = this._getColumnSize(column);
this._getResizingSiblings(column).each(function() {
totalSize += (that._getColumnSize($(this)) - 1);
that._setColumnSize($(this), 1);
});
var size = ((this.resizing.handleIsWest) ? totalSize : 1),
sizeEnd = ((this.resizing.handleIsWest) ? 1 : totalSize),
sizeOperator = ((this.resizing.handleIsWest) ? -1 : 1),
offset = 0,
offsetOperator = ((this.resizing.handleIsWest) ? 1 : 0);
var columnGutter = ((column.outerWidth(true) - column.width()) / 2),
columnWidth = ((this.resizing.handleIsWest) ? false : true);
var resizingOffsets = [];
while(true) {
this._setColumnSize(column, size);
this._setColumnOffset(column, offset);
var left = (Math.floor((column.offset()).left) + columnGutter + ((columnWidth) ? column.width() : 0));
resizingOffsets.push({
start: (left + ((columnGutter * 3) * -1)),
end: (left + (columnGutter * 3)),
size: size
});
if(size === sizeEnd) {
break;
}
size += sizeOperator;
offset += offsetOperator;
}
row.remove();
return resizingOffsets;
},
_getWidestColumn: function(columns) {
var that = this,
widestColumn;
columns.each(function() {
if(!widestColumn || (that._getColumnSize($(this)) > that._getColumnSize(widestColumn))) {
widestColumn = $(this);
}
});
return widestColumn;
},
_getNarrowestColumn: function(columns) {
var that = this,
narrowestColumn;
columns.each(function() {
if(!narrowestColumn || (that._getColumnSize($(this)) < that._getColumnSize(narrowestColumn))) {
narrowestColumn = $(this);
}
});
return narrowestColumn;
},
_getColumnSize: function(column) {
var columnSize;
$.each($.trim(column.attr('class')).split(' '), function(index, value) {
if(value.match(/^col-/) && !value.match(/-offset-/)) {
columnSize = parseInt($.trim(value).replace(/\D/g, ''), 10);
}
});
return columnSize;
},
_setColumnSize: function(column, size) {
column.toggleClass([
['col', 'sm', this._getColumnSize(column)].join('-'), ['col', 'sm', size].join('-')
].join(' '));
},
_getColumnOffset: function(column) {
var columnOffset;
$.each($.trim(column.attr('class')).split(' '), function(index, value) {
if(value.match(/^col-/) && value.match(/-offset-/)) {
columnOffset = parseInt($.trim(value).replace(/\D/g, ''), 10);
}
});
return columnOffset;
},
_setColumnOffset: function(column, offset) {
var currentColumnOffset,
toggleClasses = [];
if((currentColumnOffset = this._getColumnOffset(column)) !== undefined) {
toggleClasses.push(['col', 'sm', 'offset', currentColumnOffset].join('-'));
}
toggleClasses.push(['col', 'sm', 'offset', offset].join('-'));
column.toggleClass(toggleClasses.join(' '));
},
_destroy: function() {
this._destroyHelpers();
},
_destroyHelpers: function() {
this.element.find('.resizable-column-handle').remove();
this.element.find('.resizable-column').removeClass('resizable-column resizable-column-resizing');
this.element.find('.resizable-row').removeClass('resizable-row resizable-row-resizing');
this.element.removeClass('resizable-grid resizable-grid-resizing');
}
});
})(jQuery, window, document);
$('#layout').resizableGrid();
});
文件resizableColumns.css:
.container {
margin-top: 50px;
}
.row {
background-color: rgba(189, 195, 199, 0.25);
}
.row:hover {
background-color: rgba(189, 195, 199, 0.5);
}
.row > .col-sm-1,
.row > .col-sm-2,
.row > .col-sm-3,
.row > .col-sm-4,
.row > .col-sm-5,
.row > .col-sm-6,
.row > .col-sm-7,
.row > .col-sm-8,
.row > .col-sm-9,
.row > .col-sm-10,
.row > .col-sm-11,
.row > .col-sm-12 {
min-height: 100px;
}
.row > .col-sm-1:before,
.row > .col-sm-2:before,
.row > .col-sm-3:before,
.row > .col-sm-4:before,
.row > .col-sm-5:before,
.row > .col-sm-6:before,
.row > .col-sm-7:before,
.row > .col-sm-8:before,
.row > .col-sm-9:before,
.row > .col-sm-10:before,
.row > .col-sm-11:before,
.row > .col-sm-12:before {
content: '';
display: block;
position: absolute;
top: 0;
right: 15px;
bottom: 0;
left: 15px;
background-color: rgba(52, 152, 219, 0.25);
}
.row > .col-sm-1:hover:before,
.row > .col-sm-2:hover:before,
.row > .col-sm-3:hover:before,
.row > .col-sm-4:hover:before,
.row > .col-sm-5:hover:before,
.row > .col-sm-6:hover:before,
.row > .col-sm-7:hover:before,
.row > .col-sm-8:hover:before,
.row > .col-sm-9:hover:before,
.row > .col-sm-10:hover:before,
.row > .col-sm-11:hover:before,
.row > .col-sm-12:hover:before {
background-color: rgba(52, 152, 219, 0.5);
}
/* Resizable Grid */
.resizable-grid > .resizable-row > .resizable-column > .resizable-column-handle {
z-index: 100;
display: none;
position: absolute;
top: 0;
height: 100%;
width: 6px;
cursor: col-resize;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-ms-touch-action: none;
touch-action: none;
}
.resizable-grid > .resizable-row > .resizable-column > .resizable-column-handle-w {
left: 12px;
}
.resizable-grid > .resizable-row > .resizable-column > .resizable-column-handle-e {
right: 12px;
}
.resizable-grid > .resizable-row > .resizable-column:first-child:not(:last-child) > .resizable-column-handle-e,
.resizable-grid > .resizable-row > .resizable-column:not(:first-child):not(:last-child) > .resizable-column-handle-w,
.resizable-grid > .resizable-row > .resizable-column:not(:first-child):not(:last-child) > .resizable-column-handle-e,
.resizable-grid > .resizable-row > .resizable-column:last-child:not(:first-child) > .resizable-column-handle-w {
display: block;
}
.resizable-grid-resizing {
cursor: col-resize;
}
.resizable-grid > .resizable-row-resizing > .resizable-column:not(.resizable-column-resizing) {
opacity: 0.5;
}
将文件resizableColumns.js和resizableColumns.css放在app 文件夹的www子文件夹中。应用程序:
library(shiny)
ui <- fluidPage(
tags$head(
tags$script(src = "https://code.jquery.com/ui/1.12.1/jquery-ui.js"),
tags$link(rel = "stylesheet", href = "resizableColumns.css"),
tags$script(src = "resizableColumns.js")
),
tags$div(
id = "layout",
fluidRow(
column(
width = 3,
h3("column1")
),
column(
width = 3,
h3("column2")
),
column(
width = 3,
h3("column3")
),
column(
width = 3,
h3("column4")
)
)
)
)
server <- function(input, output){}
shinyApp(ui, server)