我正在尝试实现facebook风格的照片网格布局。我正在使用angularjs和bootstrap。我遇到过像angular-masonry这样的某些插件,我认为可以用于此。以下是我实际尝试遵循可能布局的一些快照:
知道如何实现这一目标?还有其他插件可以让生活变得轻松吗?
答案 0 :(得分:7)
所有这些都是你的可能性:
freewall - 使用jQuery
同时阅读 - An AngularJS directive for Masonry
ngPhotoGrid示例:
//ngPhotoGrid.js
angular.module("ngPhotoGrid", [])
angular.module("ngPhotoGrid")
.filter("photoUrlSafe", [
"$sce", function($sce) {
return function(text) {
return $sce.trustAsResourceUrl(text);
};
}
])
.directive('endRepeat', function() {
return {
restrict: 'A',
require: "^ngPhotoGrid",
link: function(scope, element, attrs, gridController) {
if (scope.$last) {
gridController.notifyDOMReady(element)
}
}
};
})
.directive("ngPhotoGrid", ["$templateCache", function($templateCache){
$templateCache.put("photo_grid.html",
"<ul class='photo-grid-wrapper' ng-style = 'parentStyle'><li class='grid-cell' ng-repeat= 'image in loadedImages track by $index' ng-style = 'image.cellStyle' ng-click='cellClicked(image)' end-repeat='' ng-attr-data-src='{{image[defaultOptions.urlKey] | photoUrlSafe}}'><img class='grid-cell-image' ng-style = 'image.imageStyle' ng-src='{{image[defaultOptions.urlKey]}}' alt='#'/></li></ul>");
function linker(scope, element, attrs) {
scope.loadedImages = [];
scope.loadedTakenImages = [];
scope.takenImages = [];
// ###OPTIONS
scope.defaultOptions = {
urlKey : "original_url",
sortByKey : "nth",
onClicked : function() {},
onBuilded : function() {},
onDOMReady : function() {},
margin : 2,
maxLength : 5,
isSquare : false,
buildOnLoading : true
}
angular.extend(scope.defaultOptions, scope.gridOptions);
var IS_SQUARE = scope.defaultOptions.isSquare;
var GRID_WIDTH = element.prop('offsetWidth');
var MARGIN = scope.defaultOptions.margin;
if (!GRID_WIDTH) { // set the default width of parent
GRID_WIDTH = 250
}
scope.parentStyle = { width: GRID_WIDTH + "px", overflow: "hidden", position: "relative", margin: 0, padding: 0 }
if(IS_SQUARE) {
scope.parentStyle.height = GRID_WIDTH + "px";
}
var commonStyle = {
display: 'block',
overflow: 'hidden',
cssFloat: 'left',
cursor: 'pointer',
position: 'relative'
};
//callback handler
scope.cellClicked = function(image) {
scope.defaultOptions.onClicked(image);
}
/**
* choose images from the url source to build grid
* take maximum 7 images for best looking
*------------------------------------------------*/
scope.chooseImages = function(images) {
angular.forEach(images, function(image, index) {
var randNumber; //set the id and nth value for image if does not have
randNumber = randomNumber();
image.id = image.id || randNumber;
image[scope.defaultOptions.sortByKey] = image[scope.defaultOptions.sortByKey] || randNumber;
});
var sortedImages = images.sort(function(a, b) {
return a[scope.defaultOptions.sortByKey] - b[scope.defaultOptions.sortByKey]
})
return sortedImages.slice(0, scope.defaultOptions.maxLength)
}
randomNumber = function(max) {
max = max || 999;
return Math.floor(Math.random()*max);
}
scope.preloadImages = function(images) {
scope.takenImages = scope.chooseImages(images)
angular.forEach(scope.takenImages, function(image, index) {
var img;
img = new Image();
img.id = image.id ;
img[scope.defaultOptions.sortByKey] = image[scope.defaultOptions.sortByKey];
img.onload = function(loadedImage) {
scope.loadedTakenImages.push(loadedImage);
// store the original dimesion of image
image.naturalWidth = loadedImage.target.naturalWidth
image.naturalHeight = loadedImage.target.naturalHeight
// build the grid immediatly after the image was loaded
// building while images loading
if(scope.defaultOptions.buildOnLoading) {
scope.buildPhotoGrid();
setTimeout(function() {
scope.$apply()
}, 10)
}
if(scope.loadedTakenImages.length == scope.takenImages.length) {
//trigger build completed handler
scope.defaultOptions.onBuilded(element)
//grid also can be build after all image loaded
//all image would be shown correctly, loading time cause poor UX
if(!scope.defaultOptions.buildOnLoading) {
scope.buildPhotoGrid()
setTimeout(function() {
scope.$apply()
}, 15)
}
}
};
img.src = image[scope.defaultOptions.urlKey];
});
};
scope.buildPhotoGrid = function() {
var firstImage, imageStyle, smallCellHeight,
smallCellWidth, bigCellWidth, bigCellHeight, cellCount, is2First;
// get cell style & builded options
styles = scope.getCellStyles();
smallCellHeight = styles.options.smallCellHeight;
smallCellWidth = styles.options.smallCellWidth;
bigCellWidth = styles.options.bigCellWidth;
bigCellHeight = styles.options.bigCellHeight;
cellCount = styles.options.cellCount;
is2First = styles.options.is2First;
scope.loadedImages = []
angular.forEach(scope.takenImages, function(image, index) {
if (is2First) { //case the grid has 2 image big first
var bigCellStyle, smallCellStyle;
bigCellStyle = angular.copy(styles.big);
smallCellStyle = angular.copy(styles.small);
if (index == 0) {
bigCellStyle.top = "0";
image.cellStyle = bigCellStyle;
image.imageStyle = getImageStyle(bigCellWidth, bigCellHeight, image);
} else if (index == 1) {
bigCellStyle.top = bigCellHeight + MARGIN + "px";
image.cellStyle = bigCellStyle;
image.imageStyle = getImageStyle(bigCellWidth, bigCellHeight, image);
} else {
var margin, smallCellIndex;
// fix the last cell of 2 first was not fit the grid height
if(index == scope.takenImages.length - 1) {
smallCellStyle.height = smallCellHeight + MARGIN + "px"
}
smallCellIndex = index - 2;
margin = smallCellIndex == 0 ? 0 : MARGIN;
smallCellStyle.top = smallCellIndex * smallCellHeight + (margin * smallCellIndex) + "px";
image.cellStyle = smallCellStyle;
image.imageStyle = getImageStyle(smallCellWidth, smallCellHeight, image);
}
} else if (index == 0) { //big cell style
image.cellStyle = styles.big;
image.imageStyle = getImageStyle(bigCellWidth, bigCellHeight, image);
} else if (index != cellCount - 1 || cellCount == 2){ //small cells
image.cellStyle = styles.small;
image.imageStyle = getImageStyle(smallCellWidth, smallCellHeight, image);
} else { //last small cell style (remove redundant margin right or bottom)
image.imageStyle = getImageStyle(smallCellWidth, smallCellHeight, image);
image.cellStyle = styles.last;
}
})
scope.loadedImages = scope.takenImages;
}
function getImageStyle(cellWidth, cellHeight, image) {
var imageWidth, imageHeight, curImageWidth, curImageHeight, imgRatio, cellRatio;
cellWidth = Math.round(cellWidth);
cellHeight = Math.round(cellHeight);
imageWidth = image.naturalWidth;
imageHeight = image.naturalHeight;
imgRatio = imageWidth / imageHeight;
cellRatio = cellWidth / cellHeight;
// when the any image's dimension greater than cell's dimension
if(cellWidth > imageWidth || cellHeight > imageHeight) {
if (cellWidth >= imageWidth) {
return getSmallImagePortraitStyle(cellHeight, cellWidth, imgRatio);
} else {
return getSmallImageLandscapeStyle(cellHeight, cellWidth, imgRatio);
}
} else { // when the image smaller than the cell in both dimension
if(imgRatio >= 1) {
return getSmallImageLandscapeStyle(cellHeight, cellWidth, imgRatio);
} else {
return getSmallImagePortraitStyle(cellHeight, cellWidth, imgRatio);
}
}
}
function getSmallImageLandscapeStyle(cellHeight, cellWidth, imgRatio) {
var curImageWidth = cellWidth;
var curImageHeight = Math.round(curImageWidth / imgRatio);
if(curImageHeight >= cellHeight) {
var top = (-1) * Math.round((cellWidth / imgRatio - cellHeight) / 2);
if(curImageWidth < cellWidth) {
return { width: "100%", position: "relative", top: top + "px"}
} else {
return { maxWidth: "100%", position: "relative", top: top + "px"}
}
} else {
var left = (-1) * Math.round((cellHeight * imgRatio - cellWidth) / 2);
return { maxHeight: "100%", height: "100%", position: "relative", left: left + "px"}
}
}
function getSmallImagePortraitStyle(cellHeight, cellWidth, imgRatio) {
var curImageHeight = cellHeight;
var curImageWidth = Math.round(curImageHeight * imgRatio);
var top = (-1) * Math.round((cellWidth / imgRatio - cellHeight) / 2);
var left = (-1) * Math.round((cellHeight * imgRatio - cellWidth) / 2);
if(curImageWidth <= cellWidth) {
return { width: "100%", position: "relative", top: top + "px"}
} else {
return { maxHeight: "100%", height: "100%", position: "relative", left: left + "px"}
}
}
/**
* build cell style for grid
* @firstRatio : ratio of the first image in list
* @secondRatio : ratio of the second image in list
* @cellCount : total cells in grid
*------------------------------------------------*/
buildCellStyle = function (firstImage, secondImage, cellCount) {
var firstRatio, secondRatio, bigCellStyle, smallCellStyle, lastCellStyle,
WIDTH_RATE, bigCellWidth, bigCellHeight, smallCellHeight, smallCellWidth, is2First,
case2BigImage1, case2BigImage2;
firstRatio = firstImage.naturalWidth / firstImage.naturalHeight;
if (secondImage)
secondRatio = secondImage.naturalWidth / secondImage.naturalHeight;
else
secondRatio = 1.5 //fail all cases below
bigCellStyle = angular.copy(commonStyle);
smallCellStyle = angular.copy(commonStyle);
lastCellStyle = angular.copy(commonStyle);
WIDTH_RATE = getWidthRate(firstRatio, cellCount);
case2BigImage1 = firstRatio > 0.8 && firstRatio < 1.2 &&
secondRatio > 0.8 && secondRatio < 1.2
case2BigImage2 = firstRatio >= 2 && secondRatio >= 2
if(cellCount == 2) { //build style for grid has 2 images and first image has firstRatio > 1
if(firstRatio >= 1) {
bigCellStyle.marginBottom = MARGIN;
bigCellStyle.width = GRID_WIDTH;
bigCellStyle.height = GRID_WIDTH / 2;
smallCellStyle.width = GRID_WIDTH;
smallCellStyle.height = GRID_WIDTH / 2 - MARGIN;
} else {
var marginSize = MARGIN / cellCount;
bigCellStyle.marginRight = marginSize;
smallCellStyle.marginLeft = marginSize;
if(IS_SQUARE) {
bigCellWidth = Math.floor(GRID_WIDTH / 2) - MARGIN;
bigCellStyle.width = bigCellWidth;
bigCellStyle.height = GRID_WIDTH;
smallCellWidth = Math.floor(GRID_WIDTH / 2) - MARGIN;
smallCellStyle.width = smallCellWidth;
smallCellStyle.height = GRID_WIDTH;
} else {
bigCellWidth = Math.floor(GRID_WIDTH * WIDTH_RATE) - MARGIN;
bigCellStyle.width = bigCellWidth;
bigCellStyle.height = bigCellWidth;
smallCellWidth = GRID_WIDTH - bigCellWidth - MARGIN;
smallCellHeight = bigCellWidth;
smallCellStyle.width = smallCellWidth;
smallCellStyle.height = smallCellHeight;
}
}
}
// add style for first column contain 2 big images, only support for grid has more than 5 cells
//NOTE: need check when 2 first were same size!!!
else if (cellCount >= 5 && (case2BigImage1 || case2BigImage2)) {
var GRID_HEIGHT;
WIDTH_RATE = case2BigImage1 ? 1/2 : 2/3;
scope.parentStyle.position = "relative";
bigCellStyle.cssFloat = smallCellStyle.cssFloat = lastCellStyle.cssFloat = null;
bigCellStyle.position = smallCellStyle.position = lastCellStyle.position = "absolute";
//determine the height of the big cell
//height == width / 2 if the grid in case2BigImage1
if(case2BigImage1) {
bigCellHeight = GRID_WIDTH / 2;
} else {
bigCellHeight = WIDTH_RATE * GRID_WIDTH / firstRatio;
}
GRID_HEIGHT = bigCellHeight * 2 + MARGIN; //margin bottom the first big image
scope.parentStyle.height = GRID_HEIGHT + "px";
bigCellStyle.width = GRID_WIDTH * WIDTH_RATE - MARGIN;
bigCellStyle.height = bigCellHeight;
bigCellStyle.left = 0;
smallCellStyle.width = GRID_WIDTH - bigCellStyle.width - MARGIN;
smallCellStyle.height = Math.floor((GRID_HEIGHT / (cellCount - 2))) - MARGIN;
smallCellStyle.right = 0;
is2First = true; //flag this style is has 2 big image style
lastCellStyle.height = smallCellStyle.height + MARGIN;
} else if(firstRatio >= 1) { //build style for grid more than 2 images and first image has firstRatio > 1
bigCellStyle.marginBottom = MARGIN;
smallCellStyle.marginRight = MARGIN;
var smallCellCount = cellCount - 1;
if (IS_SQUARE) {
bigCellStyle.height = GRID_WIDTH * 2 / 3;
bigCellStyle.width = GRID_WIDTH;
smallCellStyle.height = GRID_WIDTH * 1 / 3 - MARGIN;
} else {
bigCellStyle.width = GRID_WIDTH ;
bigCellStyle.height = GRID_WIDTH * 2 / 3;
}
smallCellStyle.width = ( GRID_WIDTH - smallCellCount * MARGIN ) / smallCellCount;
// determine the height of smallCell below
if (IS_SQUARE) {
smallCellStyle.height = GRID_WIDTH - bigCellStyle.height - MARGIN;
} else if (firstRatio > 1.3 && firstRatio < 1.5) { // 4:3 < firstRatio < 5:3
smallCellStyle.height = smallCellStyle.width / firstRatio;
} else if (firstRatio > 1.5) {
smallCellStyle.height = smallCellStyle.width / 1.5;
} else {
smallCellStyle.height = smallCellStyle.width;
}
lastCellStyle.height = smallCellStyle.height;
lastCellStyle.width = smallCellStyle.width;
} else { //build style for grid more than 2 images and first image has firstRatio <= 1
bigCellStyle.marginRight = MARGIN;
smallCellStyle.marginBottom = MARGIN;
if (IS_SQUARE) {
bigCellHeight = GRID_WIDTH;
bigCellWidth = GRID_WIDTH * WIDTH_RATE;
} else {
bigCellWidth = Math.floor(GRID_WIDTH * WIDTH_RATE);
bigCellHeight = bigCellWidth / firstRatio;
}
bigCellStyle.width = bigCellWidth;
bigCellStyle.height = bigCellHeight;
smallCellCount = cellCount - 1;
smallCellWidth = GRID_WIDTH - bigCellWidth - MARGIN;
smallCellHeight = bigCellHeight / smallCellCount - MARGIN
smallCellStyle.width = GRID_WIDTH - bigCellWidth - MARGIN;
smallCellStyle.height = smallCellHeight;
lastCellStyle.width = smallCellWidth;
lastCellStyle.height = smallCellHeight;
}
return {
big: bigCellStyle,
small: smallCellStyle,
last: lastCellStyle,
options: {
firstRatio: firstRatio,
// keep these value because ng style need add measured suffix
smallCellWidth: smallCellStyle.width,
smallCellHeight: smallCellStyle.height,
bigCellWidth: bigCellStyle.width,
bigCellHeight: bigCellStyle.height,
cellCount: cellCount,
is2First: is2First
} //keep these values to style cell image after building style for cell link
}
}
getWidthRate = function(firstRatio, cellCount) {
if (cellCount == 2) { //build style for 2 images
if(firstRatio > 1) {
return 2/3;
} else {
return 1/2;
}
} else if(firstRatio > 1) { //build style for >= 3 images, first image has firstRatio > 1
return 1
} else { //build style for >= 3 images, first image has firstRatio < 1
return 2/3
}
}
scope.getCellStyles = function() {
var firstImage, secondImage, cellCount, buildedStyle;
firstImage = scope.takenImages[0];
secondImage = scope.takenImages[1];
cellCount = scope.takenImages.length;
if (cellCount == 1) { //build style for only one image
//@todo need implement!
} else { //build style for >=2 images
buildedStyle = buildCellStyle(firstImage, secondImage, cellCount);
}
// remove margin right of last small cell in the bottom
if(buildedStyle.small.marginRight) {
buildedStyle.last.marginRight = 0;
buildedStyle.last.width = buildedStyle.small.width + MARGIN;
}
// remove margin bottom of last small cell in the right
if(buildedStyle.small.marginBottom) {
buildedStyle.last.marginBottom = 0;
buildedStyle.last.height = buildedStyle.small.height + MARGIN;
}
// add suffix px for margin and size for ng-style working
var attrs = ["width", "height", "marginRight", "marginLeft", "marginBottom", "left", "right"];
angular.forEach(attrs, function(attr, index) {
if(buildedStyle.big[attr]) {
buildedStyle.big[attr] += "px";
}
if(buildedStyle.small[attr]) {
buildedStyle.small[attr] += "px";
}
if(buildedStyle.last[attr]) {
buildedStyle.last[attr] += "px";
}
})
return buildedStyle;
}
//trigger build grid
scope.$watch("images", function(images) {
if(images && images.length > 0) {
scope.preloadImages(images);
}
})
}
return {
restrict: "A",
templateUrl: "photo_grid.html",
scope: {
images: "=",
gridOptions: "="
},
controller: ["$scope", "$element", function($scope, $element) {
this.notifyDOMReady = function() {
$scope.defaultOptions.onDOMReady($element)
}
}],
link: linker
}
}])
angular.module("ngApp", ["ngPhotoGrid"])
angular.module("ngApp").controller("indexCtrl", ["$scope", function($scope){
//show loading mark while grid is building
$scope.isBuildingGrid = true;
// production test
img1 = {original_url: "http://lorempixel.com/1366/768"};
img2 = {original_url: "http://lorempixel.com/316/316"};
img3 = {original_url: "http://lorempixel.com/400/200"};
img4 = {original_url: "http://lorempixel.com/600/1000"};
img5 = {original_url: "http://lorempixel.com/600/800"};
img6 = {original_url: "http://lorempixel.com/800/600"};
img7 = {original_url: "http://lorempixel.com/800/800"};
img8 = {original_url: "http://lorempixel.com/900/1000"};
// // local dev
// img1 = {original_url: "images/1366x768.jpg"};
// img2 = {original_url: "images/316x316.jpg"};
// img3 = {original_url: "images/600x1000.jpg"};
// img4 = {original_url: "images/900x1000.jpg"};
// img5 = {original_url: "images/600x800.jpg"};
// img6 = {original_url: "images/800x600.jpg"};
// img7 = {original_url: "images/800x800.jpg"};
// img8 = {original_url: "images/900x1000.jpg"};
var sources = [img1, img2, img3, img4, img5, img6, img7, img8]
var sources2big = [{original_url: "http://lorempixel.com/316/316", nth: 1}, {original_url: "http://lorempixel.com/800/800", nth: 2}, img3, img4, img5, img6]
$scope.rand = function(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
$scope.clickHandler = function(image) {
alert(JSON.stringify(image))
}
$scope.buildCompletedHandler = function() {
console.log ("built completed!")
$scope.isBuildingGrid = false;
}
getSelectedSeeds = function() {
var photoNumbers = $scope.rand(2, 7)
var seeds = []
var arr = []
while(arr.length < photoNumbers){
var randomnumber = $scope.rand(1, 8);
var found = false;
for(var i = 0; i < arr.length; i++){
if(arr[i] == randomnumber ){
found = true;
break;
}
}
if(!found) {
arr[arr.length] = randomnumber;
seeds.push(sources[randomnumber])
}
}
return seeds;
}
$scope.images = getSelectedSeeds();
$scope.images2big = sources2big.slice(0, 7);
/**
* Options definitions
*----------------------*/
$scope.gridOptions = {
urlKey : "original_url",
sortKey : "nth",
onClicked : function(image) {
alert(JSON.stringify(image))
},
onBuilded : function() {
console.log ("built completed!")
$scope.isBuildingGrid = false;
},
margin : 2,
maxLength : 5
}
/**
* Options definitions for square example
*----------------------------------------*/
$scope.gridOptionsSquare = {
urlKey : "original_url",
sortKey : "nth",
onClicked : function(image) {
alert(JSON.stringify(image))
},
onBuilded : function() {
console.log ("built completed!")
$scope.isBuildingGrid = false;
},
margin : 2,
isSquare : true,
maxLength : 4
}
$scope.squareGridGroup = [];
angular.forEach([1,2,3,4,5,6], function() {
$scope.squareGridGroup.push(angular.copy(getSelectedSeeds()))
})
}])
/**
* All these styles are not a part of angular module.
*/
.center {
text-align: center;
}
.small {
font-size:12px;
font-weight: normal;
margin-left: 10px;
}
.wrapper {
text-align: center;
}
.content {
width: 400px;
margin: 0 auto;
}
.feed-item{
overflow: hidden;
}
.feed-photos {
position: relative;
min-height: 100px;
}
.feed-photos.loading::after {
content: "";
height: 100%;
width: 100%;
background: rgba(0,0,0,.6) url("loader.gif") center center no-repeat;
left:0;
top:0;
position: absolute;
}
.feed-photos.loading .grid-cell-image{
width: 100%;
}
/**
* It's the part of module
* used to style the border of grid
* included this style if you want to border the grid cell
*/
.grid-cell:after {
content: '';
position: absolute;
border: 1px solid rgba(0, 0, 0, 0.2); /*change this color if you want*/
top: 0;
right: 0;
bottom: 0;
left: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<style>
body { text-align: center; }
.links a { padding: 0 5px ;}
.active { color: red;}
</style>
</head>
<body ng-app='ngApp'>
<div class='wrapper' ng-controller="indexCtrl">
<div class="header">
<h1>ngPhotoGrid Example - 2 bigs first</h1>
</div>
<div class='content'>
<div class="feed-item">
<div ng-photo-grid = ""
images = "images2big"
grid-options = "gridOptions"
ng-class = "{'loading': isBuildingGrid}"
class = "feed-photos">
</div>
</div>
<p class='small'><i>click on image or turn on firebug to see the callback.</i></p>
</div>
<div class='footer'>
<p><a href="https://github.com/jerryc-nguyen/ng-photo-grid" target="_blank">source on github</a></p>
</div>
</div>
答案 1 :(得分:2)
您是否尝试过 ngPhotoGrid ? https://github.com/jerryc-nguyen/ng-photo-grid
简单的紧凑型照片网格,如AngularJS中的Facebook,没有依赖关系。
从Examples查看它看起来就像你要找的那样。
答案 2 :(得分:2)
使用纯css有一种更简单的方法:ng-messages
和column-count
。
这是一个小提琴(https://jsfiddle.net/3z73obt0/1/)
以下是代码:
column-width
&#13;
#wrapper {
-moz-column-count: 2;
-webkit-column-count: 2;
column-count: 2;
-moz-column-gap: 10px;
-webkit-column-gap: 10px;
column-gap: 10px;
}
#wrapper > div:nth-child(n+2) {
margin-top:10px;
}
#wrapper > div:nth-child(1) {
height: 150px;
width:100%;
background-color:#8ebce5;
}
#wrapper > div:nth-child(2) {
height: 150px;
width:100%;
background-color:#3a2313;
}
#wrapper > div:nth-child(3) {
height: 100px;
width:100%;
background-color:peru;
}
#wrapper > div:nth-child(4) {
height: 100px;
width:100%;
background-color:#BB3579;
}
#wrapper > div:nth-child(5) {
height: 100px;
width:100%;
background-color:#EAC243;
}
&#13;
如果您仍然喜欢使用库,我建议您查看同位素库来实现这一目标。 (http://isotope.metafizzy.co/)。
这是一个使用同位素制作的漂亮砖石布局的编码:http://codepen.io/desandro/pen/mIkhq