Tạo grid CSS tùy biến với SCSS

Đăng bởi Neo trong mục Hướng dẫn vào 07 tháng 2, 2017 | Comments

Nhiều bạn dùng Bootstrap (hay CSS framework khác) để chia layout quen nhưng không thực sự hiểu rõ cách hoạt động của chúng. Và khi cần tùy biến hoặc thay đổi giá trị của grid thì gặp nhiều khó khăn.

Hôm nay mình sẽ hướng dẫn các bạn cách tạo 1 grid tương tự như của bootstrap với cấu trúc container, row, col-xs-x => col-sm-x => col-md-x => col-lg-x (theo mobile first). Với x là giá trị column từ 1 đến số column cao nhất bạn đặt ví dụ 12.

Bài viết cần kiến thức cơ bản về HTML, CSS và SCSS. Sau bài viết bạn sẽ có một công cụ giúp tạo được grid bất kỳ chỉ trong vài giây. Phần hướng dẫn chỉ nói chính về SCSS, để hiểu rõ hơn bạn có thể xem demo.

Bước 1

Đầu tiên để tạo 1 grid css, bạn sẽ cần đoạn SCSS như sau:

// Reset body
body {
	margin: 0;
	padding: 0;
}
*,
*:before,
*:after {
	box-sizing: border-box;
}
// Ví dụ với container rộng 1200px
.container {
	max-width: 1200px;
	margin: auto;
}
// Container full chiều rộng
.container-fluid {
	width: 100%;
}
// Container padding 2 bên là 15px
.container,
.container-fluid {
	padding-left: 15px;
	padding-right: 15px;
}
// Tạo row để fix khoảng cách 2 bên bằng với khoảng cách 2 bên của column và fix chiều cao do column float: left tạo ra bên dưới
.row {
	margin-left: -15px;
	margin-right: -15px;
	&:after {
		content: '';
		display: table;
		clear: both;
	}
}
// Column cho khoảng cách 30px
[class*="col-"] {
	float: left;
	padding-left: 15px;
	padding-right: 15px;
}
// Với grid là 12 column thì col-xs-3 sẽ là 25%
.col-xs-3 {
	width: 25%;
}

Với những bạn chưa biết về scss sẽ thấy đoạn code trên khá giống với CSS, chỉ khác một chút ở phần .row>

Bước 2

Với đoạn code SCSS phần trước, chúng ta đã có 1 grid với 1 column là col-xs-3. Tiếp theo chúng ta sẽ đặt biến để sau này chỉnh setting như sau:

$containerWidth: 1200; // rộng container
$containerGap: 15; // khoảng cách 2 bên container
$colGap: 30; // khoảng cách 2 bên của column
$colMax: 12; // Số column của grid

// Reset body
body {
  margin: 0;
  padding: 0;
}
*,
*:before,
*:after {
	box-sizing: border-box;
}
// Ví dụ với container rộng 1200px, ở đây ta thay bằng biến $containerWidth
.container {
	max-width: $containerWidth * 1px;
	margin: auto;
}
// Container full chiều rộng
.container-fluid {
	width: 100%;
}
// Container padding 2 bên là 15px
.container,
.container-fluid {
	padding-left: $containerGap;
	padding-right: $containerGap;
}
// Tạo row để fix khoảng cách 2 bên bằng với khoảng cách 2 bên của column và fix chiều cao do column float: left tạo ra bên dưới
.row {
	margin-left: -($colGap / 2) * 1px;
	margin-right: -($colGap / 2) * 1px;
	&:after {
		content: '';
		display: table;
		clear: both;
	}
}
// Column cho khoảng cách 30px
[class*="col-"] {
	float: left;
	padding-left: ($colGap / 2) * 1px;
	padding-right: ($colGap / 2) * 1px;
}
// Với grid là 12 column thì col-xs-3 sẽ là 25%
.col-xs-3 {
	width: (100 * 3) / $colMax * 1%; // tính column ví dụ với col-xs-3 sẽ ra 25%
}

Bước 3: Xử lý column

Ở trên ta có col-xs-3 với cấu trúc như sau

.col-xs-3 {
	width: (100 * 3) / $colMax * 1%; // tính column ví dụ với col-xs-3 sẽ ra 25%
}

Tiếp theo để tạo các column từ 1 đến $colMax (là 12), ta sẽ dùng vòng for

@for $i from 1 through $colMax {
	.col-xs-#{$i} {
	  	width: (100 * $i) / $colMax * 1%;
	}
}

Khi đó css sinh ra ta sẽ được các col-xs-1, col-xs-2, ... col-xs-12

Bước 3b: Responsive

Như đã nói ở phần đầu, grid này là mobile first nên col-xs-x sẽ là mặc định, khi đó có CSS sẽ như sau:

@media (min-width: 768px) {
	// col-sm-x >= 768px
}
@media (min-width: 992px) {
	// col-md-x >= 992px
}
@media (min-width: 1200px) {
	// col-lg-x >= 1200px
}

ta sẽ đặt 1 biến $breakpoints tính các màn hình từ nhỏ tới lớn ví dụ như sau

$breakpoints: (
	default: xs,
	sm: 768,
	md: 992,
	lg: 1200
);

Để gán key và value trong $breakpoints lần lượt vào các column, trước hết ta sẽ đặt vòng for cho column ở trên thành 1 mixin:

@mixin column($nameCol) {
	@for $i from 1 through $colMax {
		.col-#{$nameCol}-#{$i} {
		  	width: (100 * $i) / $colMax * 1%;
		}
	}
}

Ta sẽ dùng each và if else và include mixin trên vào như sau để khi là default thì kết quả sẽ cho ra col-xs-x bình thường còn không thì code css sẽ nằm trong @media

@each $key, $val in $breakpoints {
	@if ($key == 'default') {
		// Dùng hàm map-get của scss để lấy value
		$colDefault: map-get($breakpoints, 'default');
		@include column($colDefault);
	} @else {
		@media (min-width: $val * 1px) {
			@include column($key);
		}
	}
}

Bước 4: Hoàn thiện

Để file SCSS cuối cùng thực sự chuyên nghiệp, chúng ta sẽ tạo 1 mảng $grid để làm setting. Bạn chỉ cần chuyển hết các biến đã đặt làm setting trên kia vào mảng và dùng map-get để lấy ra

$grid: (
	container: (
		width: 1200,
		gap: 15
	),
	columns: (
		max: 12,
		gap: 30
	),
	breakpoints: (
		default: xs,
		sm: 768,
		md: 992,
		lg: 1200
	)
);

scss cung cấp cho 1 hàm map-get nhưng để lấy value nằm sâu bên trong ta sẽ viết 1 hàm map-deep-get như sau

// Map deep get
@function map-deep-get($map, $keys...) {
	@each $key in $keys {
		$map: map-get($map, $key);
	}
	@return $map;
}

Viết lại biến dùng map-deep-get để lấy giá trị trong $grid

$containerWidth: map-deep-get($grid, 'container', 'width');
$containerGap: map-deep-get($grid, 'container', 'gap');
$colMax: map-deep-get($grid, 'columns', 'max');
$colGap: map-deep-get($grid, 'columns', 'gap');

body {
	margin: 0;
	padding: 0;
}
*,
*:before,
*:after {
	box-sizing: border-box;
}
.container {
	max-width: $containerWidth * 1px;
	margin: auto;
}
.container-fluid {
	width: 100%;
}
.container,
.container-fluid {
	padding-left: $containerGap * 1px;
	padding-right: $containerGap * 1px;
}
.row {
  	margin-left: -($colGap / 2) * 1px;
	margin-right: -($colGap / 2) * 1px;
	&:after {
		content: '';
		display: table;
		clear: both;
	}
}
[class*="col-"] {
	float: left;
	padding-left: ($colGap / 2) * 1px;
	padding-right: ($colGap / 2) * 1px;
}

@mixin column($nameCol) {
	@for $i from 1 through $colMax {
		.col-#{$nameCol}-#{$i} {
		  width: (100 * $i) / $colMax * 1%;
		}
	}
}
// Dùng map-get để viết lại thay cho biến $breakpoints
@each $key, $val in map-get($grid, 'breakpoints') {
	@if ($key == 'default') {
		$colDefault: map-deep-get($grid, 'breakpoints', 'default');
		@include column($colDefault);
	} @else {
		@media (min-width: $val * 1px) {
			@include column($key);
		}
	}
}

Vậy là bạn đã đã hoàn thiện 1 grid đơn giản với setting = SCSS. Để build ra CSS, bạn có thể dùng gulp hoặc tool như prepros.

Ví dụ về việc thay đổi các giá trị container, gap và responsive để tạo một custom grid:

$grid: (
	container: (
		width: 1500,
		gap: 20
	),
	columns: (
		max: 12,
		gap: 30
	),
	breakpoints: (
		default: xs,
		test: 500,
		tablet: 768,
		md: 992,
		lg: 1200
	)
);

Bạn sẽ có được 1 container với rộng 1500px, khoảng cách 2 bên 20px. Và column responsive bạn sẽ có thêm col-test-x, col-tablet-x với màn hình từ 500px và 768px trở lên. Bạn có thể xem file CSS sinh ra ở đây.

Hãy viết thêm setting để hoàn thiện hơn nữa và chia sẻ cho mọi người nhé...

Bạn có thích bài viết này?

Neo's picture

Neo

Nhìn mặt trời từ năm 1984 nhưng tới tận 2002 mới được thấy cái máy tính đầu tiên của mình. Đầu năm 2007 thì quyết định theo cái nghề cao quý là thiết kế web Big Grin. Hiện mình đang sống tại Hà Nội. Sở thích: làm website và giúp đỡ mọi người phát triển website theo chiều hướng tốt đẹp hơn.

Trang chủ - Twitter