Monday, October 7, 2013

Efek Masonry Hanya dengan CSS

CSS3 Masonry Layout

Teknik ini sudah pernah dibahas dengan cukup detail di CSS-Tricks. Hanya saja Saya tidak habis pikir mengapa beliau tetap mempertahankan deklarasi column-count untuk memecah area menjadi beberapa kolom dan menggunakan media queries untuk mengurangi jumlah kolom pada saat ukuran layar peramban menyempit. Padahal jika kita sudah menentukan lebar masing-masing item dengan ukuran yang sama, kita bisa menggunakan column-width untuk menentukan lebar kolom tetap tanpa harus melibatkan media queries untuk mengurangi jumlah kolom pada saat ukuran layar semakin sempit. column-width akan menciptakan kolom-kolom dengan jumlah yang bisa menyesuaikan diri berdasarkan ruang yang tersisa. Sudah tertulis dengan jelas dalam spesifikasi:

.container {
  column-width:150px;
  column-gap:5px; /* Margin kanan/kiri antarkolom */
}

img {
  display:block;
  width:100%;
  height:auto;
  margin:0 0 5px 0; /* Margin bawah antargambar */
}

Lihat Demo

Item Bukan Gambar

Ada satu hal yang harus diperhatikan jika Anda ingin menciptakan efek/tata letak seperti ini pada elemen yang bukan merupakan gambar, yaitu deklarasi display berupa inline-block. Mendeklarasikan perintah ini akan mencegah perpotongan yang tak terduga pada masing-masing item karena CSS3 Kolom secara normal akan berusaha untuk membuat masing-masing kolom menjadi sama tinggi. Beberapa harus terpaksa dipotong di bagian bawah mengingat properti CSS ini memang sebenarnya berbasis teks:

.container {
  column-width:150px;
  column-gap:5px; /* Margin kanan/kiri antarkolom */
}

.item {
  display:inline-block; /* Mencegah pemotongan item yang tak terduga */
  margin:0 0 5px 0; /* Margin bawah antaritem */
  padding:10px;
  background-color:black;
  color:white;
}

Lihat Demo


Ini yang Saya maksud sebagai perpotongan yang tidak diduga dan tidak dikehendaki:

Item terpotong pada akhir kolom pada tempat yang salah.
Teks terpotong pada bagian bawah untuk memastikan agar masing-masing kolom memiliki tinggi yang sama.

Tidak tahu apa itu Masonry?

Labels: ,

Sunday, October 6, 2013

JavaScript Table Sorter

Skrip ini bisa digunakan untuk menyortir tabel dengan tampilan yang sederhana:

JavaScript

// Original code: http://stackoverflow.com/a/14268260/1163000
// Usage: `makeSortable(elem);`
(function() {
    function sortTable(table, col, reverse) {
        var tb = table.tBodies[0], // Use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
            tr = Array.prototype.slice.call(tb.rows, 0); // Put rows into array
        reverse = -((+reverse) || -1);
        tr = tr.sort(function (a, b) { // Sort rows
            return reverse * (a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()));
        });
        for (var i = 0, len = tr.length; i < len; ++i) {
            tb.appendChild(tr[i]); // Append each row in order
        }
    }
    function makeSortable(table) {
        var th = table.tHead, i;
        th && (th = th.rows[0]) && (th = th.cells);
        if (th) {
            i = th.length;
        } else {
            return; // If no `<thead>` then do nothing
        }
        while (--i >= 0) (function(i) {
            var dir = 1;
            th[i].onmousedown = function() {
                for (var j = 0, jen = th.length; j < jen; ++j) {
                    th[j].className = th[j].className.replace(/(^| )desc|asc( |$)/g, "$1$2");
                }
                sortTable(table, i, (dir = 1 - dir));
                this.className += dir === 1 ? " desc" : " asc";
                return false;
            };
        }(i));
    }
    window.makeSortable = makeSortable;
})();

CSS

.table-sortable {
  border-collapse:collapse;
  table-layout:fixed;
  width:100%;
  font:normal normal 13px/1.4 Arial,Sans-Serif;
  color:black;
  background-color:white;
}

.table-sortable th,
.table-sortable td {
  margin:0;
  padding:5px 8px;
  border:none;
  vertical-align:top;
  text-align:left;
}

.table-sortable th {
  font-weight:bold;
  background-color:slategray;
  color:white;
  border-color:white;
}

.table-sortable tbody tr:nth-child(even) {background-color:whitesmoke}
.table-sortable th {cursor:pointer}

th.asc,
th.desc {background-color:lightslategray}

th.asc:before,
th.desc:before {
  content:"";
  display:block;
  float:right;
  width:0;
  height:0;
  border:.4em solid transparent;
}

th.asc:before {
  border-bottom-color:inherit;
  border-top-width:0;
  margin-top:.4em;
}

th.desc:before {
  border-top-color:inherit;
  border-bottom-width:0;
  margin-top:.5em;
}

HTML

<table class="table-sortable" id="table-1">
  <thead>
    <tr><th>Header 1</th><th>Header 2</th><th>Header 3</th></tr>
  </thead>
  <tbody>
    <!-- Sortable rows -->
    <tr><td> ... </td><td> ... </td><td> ... </td></tr>
    <tr><td> ... </td><td> ... </td><td> ... </td></tr>
    <tr><td> ... </td><td> ... </td><td> ... </td></tr>
    ...
  </tbody>
</table>
<script>makeSortable(document.getElementById('table-1'));</script>

Meski masih ada sedikit kekurangan karena fungsi sortir ini hanya memakai logika berdasarkan abjad. Coba Anda klik pada header Harga. Hasilnya tidak akan sesuai dengan apa yang kita harapkan. Masih sedang mencari solusi yang paling mudah untuk menangani satuan harga pada fungsi ini…

Demo

Lihat Demo

Labels: ,

Tuesday, October 1, 2013

JavaScript Image Trail Tooltip

SXC Browse Page
Halaman browse foto di SXC.

Pertama kali Saya melihat tooltip semacam ini di situs SXC. Saya mencoba untuk membuat replikanya dengan tanpa menyertakan dukungan untuk peramban-peramban lawas. document.all itu sudah sangat ketinggalan zaman!

Keistimewaan dari tooltip ini adalah dia bisa bergerak mengikuti gerakan pointer serta bisa mempertahankan posisinya agar tetap berada pada area yang terlihat di halaman agar Anda tidak mengalami masalah-masalah seperti: tooltip keluar terlalu jauh ke sebelah kanan atau ke sebelah bawah dari area terlihat pada halaman. JavaScript ini juga bisa digunakan untuk menggantikan salah satu plugin jQuery Saya yang tidak begitu terkenal dan tidak sering diperbaharui lagi:

JavaScript

/*! JavaScript Image Trail Tooltip by Taufik Nurrohman <http://gplus.to/tovic> */
(function(w, d) {

    var tooltip = d.createElement('div'),
        noImage = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", // 1 x 1 pixel transparent GIF
        top = 0,
        left = 0,
        docWidth = 0,
        docHeight = 0,
        offsetTop = 20, // Default top distance of the tooltip to the mouse pointer
        offsetLeft = 20, // Default left distance of the tooltip to the mouse pointer
        wait = null;

    // Get the correct width of the document without scrollbars
    function getDocWidth() {
        return d.documentElement.clientWidth;
    }

    // Get the correct height of the document
    function getDocHeight() {
        return Math.max(
            d.body.scrollHeight, d.documentElement.scrollHeight,
            d.body.offsetHeight, d.documentElement.offsetHeight,
            d.body.clientHeight, d.documentElement.clientHeight
        );
    }

    tooltip.id = "trail-image";
    tooltip.className = "trail-image";
    tooltip.innerHTML = '<img src="' + noImage + '" alt="Loading..." style="float:none;display:block;width:100%;height:100%;max-width:none;max-height:none;min-width:0;min-height:0;border:none;outline:none;background:none;margin:0;padding:0;">';

    // Just like `DOMContentLoaded` event, but only to
    // wait for the existence of the `<body>` element
    // to insert the tooltip markup in the proper area
    function waitForBodyExist() {
        if (!d.body) {
            wait = setTimeout(waitForBodyExist, 100);
        } else {
            clearTimeout(wait);
            d.body.appendChild(tooltip);
            docWidth = getDocWidth();
            docHeight = getDocHeight();
            w.onresize = function() {
                docWidth = getDocWidth();
                docHeight = getDocHeight();
            };
            w.onscroll = hideTrail;
        }
        // console.log('Still waiting...');
    } waitForBodyExist();

    // Function to show the tooltip
    // `width`  => the tooltip width
    // `height` => the tooltip height
    // `file`   => the URL of the image to show
    function showTrail(width, height, file) {
        tooltip.style.visibility = "visible";
        tooltip.children[0].src = file;
        tooltip.style.width = parseInt(width, 10) + "px";
        tooltip.style.height = parseInt(height, 10) + "px";
        d.onmousemove = function(e) {
            if (!e) e = w.event;
            if (e.pageX || e.pageY) {
                left = e.pageX;
                top = e.pageY;
            } else if (e.clientX || e.clientY) {
                left = e.clientX + d.body.scrollLeft + d.documentElement.scrollLeft;
                top = e.clientY + d.body.scrollTop + d.documentElement.scrollTop;
            }
            tooltip.style.top = parseInt(((top >= docHeight - (height + offsetTop + 10)) ? top - (height + offsetTop) : top + offsetTop), 10) + "px";
            tooltip.style.left = parseInt(((left >= docWidth - (width + offsetLeft + 10)) ? left - (width + offsetLeft) : left + offsetLeft), 10) + "px";
        };
    }

    // Function to hide the tooltip
    function hideTrail() {
        d.onmousemove = "";
        tooltip.style.top = "-9999px";
        tooltip.style.left = "-9999px";
        tooltip.style.visibility = "hidden";
        tooltip.children[0].src = noImage;
        docWidth = getDocWidth();
        docHeight = getDocHeight();
    }

    // Add to the window object as an external/global function
    w.showTrail = showTrail;
    w.hideTrail = hideTrail;

})(window, document);

CSS

/* Image Trail Tooltip CSS */
.trail-image {
  width:0;
  height:0;
  background-color:#ddd;
  border:1px solid #888;
  position:absolute;
  top:-9999px;
  left:-9999px;
  z-index:9999;
  visibility:hidden;
  -webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);
  -moz-box-shadow:0 1px 1px rgba(0,0,0,.2);
  box-shadow:0 1px 1px rgba(0,0,0,.2);
}

Penggunaan

Dasar Penggunaan

Ada dua fungsi utama dalam skrip di atas, yaitu showTrail dan hideTrail. showTrail digunakan untuk menampilkan tooltip sedangkan hideTrail digunakan untuk menyembunyikannya. Anda bisa menerapkannya pada atribut onmouseover dan onmouseout seperti ini:

<a href="img/large.jpg">
  <img onmouseover="showTrail(250, 100, &#39;img/medium.jpg&#39;);" onmouseout="hideTrail();" src="img/small.jpg" alt="">
</a>

Tentukan lebar dan tinggi gambar baru pada parameter width dan height serta URL gambar baru yang ingin ditampilkan pada parameter file:

Lihat Demo

Menyimpan Data di dalam Atribut HTML

Menuliskan fungsi berkali-kali secara inline mungkin akan melelahkan. Untuk itu Anda bisa memodifikasinya dengan cara memasukkan setiap URL gambar yang lebih besar di dalam atribut HTML5 data-*:

<img alt="" src="img/small.jpg" data-image-preview="img/medium.jpg">

Kemudian untuk penerapan fungsinya:

(function() {
    var img = document.getElementsByTagName('img');
    for (var i = 0, len = img.length; i < len; ++i) {
        if (img[i].getAttribute('data-image-preview')) {
            img[i].onmouseover = function() {
                showTrail(this.offsetWidth * 3, this.offsetHeight * 3, this.getAttribute('data-image-preview'));
            };
            img[i].onmouseout = hideTrail;
        }
    }
})();

Pada kode di atas Saya menerapkan this.offsetWidth * 3 dan this.offsetHeight * 3 untuk menciptakan skala tooltip menjadi tiga kali lipat dari gambar thumbnail yang didekati pointer. Jadi anggap saja URL yang berada di dalam atribut data-image-preview memiliki ukuran sebesar tiga kali lipat dari thumbnail.

Pastikan DOM sudah berada dalam keadaan siap:

Lihat Demo

Tip untuk Gambar-Gambar dari Picasa

Kita tahu bahwa kita bisa mengubah resolusi gambar yang disimpan di Picasa dengan cara mengubah path /sN pada setiap gambar. Berikut ini adalah contoh penerapan fungsi showTrail dan hideTrail tanpa memerlukan adanya atribut data-image-preview. Sekali Anda menerapkan fungsi ini, maka semua gambar yang berasal dari Picasa dengan ukuran lebar tidak lebih dari 100 piksel akan bisa menampilkan tooltip dengan lebar gambar 400 piksel:

(function() {
    var img = document.getElementsByTagName('img');
    for (var i = 0, len = img.length; i < len; ++i) {
        var valid = (img[i].src && /\/s[0-9]+(\-c)?\/.*?\.(bmp|gif|jpg|jpeg|png)/i.test(img[i].src) && img[i].offsetWidth <= 100);
        if (valid) {
            img[i].onmouseover = function() {
                showTrail(this.offsetWidth * 4, this.offsetHeight * 4, this.src.replace(/\/s[0-9]+(\-c)?/, "/s400$1"));
            };
            img[i].onmouseout = hideTrail;
        }
    }
})();

Lihat Demo

Pembaharuan: Pengecekan lebar gambar yang Saya terapkan di atas terkadang tidak bekerja karena mungkin fungsi telah tereksekusi sebelum dimensi gambar terbentuk, sehingga lebar gambar akan sama dengan 0 dan itu membuatnya bisa lolos dari pengecekan. Anda bisa mengeluarkan logika img[i].offsetWidth <= 100 dan memasukkannya ke dalam event onmouseover seperti ini untuk memastikan agar pengecekan lebar gambar bisa dilakukan setelah dimensi gambar terbentuk setiap kali Anda mendekatkan pointer ke atas gambar:

var valid = (img[i].src && /\/s[0-9]+(\-c)?\/.*?\.(bmp|gif|jpg|jpeg|png)/i.test(img[i].src));
if (valid) {
    img[i].onmouseover = function() {
        if (this.offsetWidth <= 100) {
            showTrail(this.offsetWidth * 4, this.offsetHeight * 4, this.src.replace(/\/s[0-9]+(\-c)?/, "/s400$1"));
        }
    };
    img[i].onmouseout = hideTrail;
}

Lihat Demo

Catatan Tambahan untuk Internet Explorer

Pastikan deklarasi <!DOCTYPE html> dinyatakan pada dokumen HTML Anda agar skrip ini bisa bekerja dengan baik. Selain itu, menambahkan tag meta ini sedekat mungkin dengan <head> juga bisa mencegah Internet Explorer untuk berubah ke mode compatibility view tanpa kita kehendaki:

<meta http-equiv='X-UA-Compatible' content='IE=Edge'>

Labels: ,