KendoGrid滚动问题:页面滚动表头固定与水平滚动条需求
KendoGrid 滚动相关问题解决
问题背景
在门户中使用KendoGrid时,移除了网格自带滚动条改用窗口滚动条,导致网格高度过长时,用户滚动到页面底部表头会不可见。
技术诉求
- 页面滚动时让表头保持固定(原生粘性表头因网格无滚动未触发)
- 若当前结构不理想,可为网格添加顶部或上下双水平滚动条
解决方案
一、实现页面滚动时表头固定
KendoGrid原生粘性表头依赖网格自身滚动容器,改用窗口滚动后需通过自定义CSS和JS实现:
- 添加固定表头CSS
/* 固定表头,若页面有顶部导航栏需调整top值为导航栏高度 */ .k-grid-header { position: sticky; top: 0; z-index: 100; background-color: #fff; /* 匹配主题背景色 */ }
- 同步表头与内容列宽
在dataBound事件中确保表头列和内容列宽度一致,避免错位:
function onDataBound(e) { var grid = this; // 同步列宽 grid.thead.find("th").each(function(index) { var contentWidth = grid.tbody.find("td").eq(index).width(); $(this).width(contentWidth); }); // 原有dataBound逻辑... }
二、添加上下双水平滚动条
若需要保留网格水平滚动并添加顶部滚动条,可通过复制底部滚动条并同步滚动事件实现:
- 调整网格配置
启用网格滚动,移除固定高度或设置适配窗口的高度:
$("#grid").kendoGrid({ // height: $(window).height() - 100, // 可选:设置网格高度 scrollable: true, // 启用滚动 // 其他配置... });
- 添加顶部滚动条并同步滚动
$(document).ready(function() { // 网格初始化完成后执行 var grid = $("#grid").data("kendoGrid"); var topScroll = $("<div class='k-grid-scrollbar-top'></div>").insertBefore(grid.wrapper); var bottomScroll = grid.wrapper.find(".k-grid-content"); // 双向同步滚动位置 topScroll.scroll(function() { bottomScroll.scrollLeft($(this).scrollLeft()); }); bottomScroll.scroll(function() { topScroll.scrollLeft($(this).scrollLeft()); }); // 设置顶部滚动条样式 topScroll.css({ overflowX: "scroll", overflowY: "hidden", height: "18px", width: bottomScroll.width() }); // 添加占位元素让滚动条可拖动 topScroll.append($("<div></div>").css({ width: bottomScroll[0].scrollWidth, height: 1 })); });
- 补充顶部滚动条CSS
.k-grid-scrollbar-top { margin-bottom: -1px; border-bottom: 1px solid #ddd; /* 匹配主题边框色 */ }
修改后的完整代码
<!DOCTYPE html> <html> <head> <title></title> <link href="https://kendo.cdn.telerik.com/themes/7.0.2/default/default-main.css" rel="stylesheet" /> <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script> <script src="js/kendo.all.min.js"></script> </head> <body> <script src="https://unpkg.com/jszip/dist/jszip.min.js"></script> <div id="example"> <div id="grid"></div> <script> $(document).ready(function () { var crudServiceBaseUrl = "https://demos.telerik.com/kendo-ui/service", dataSource = new kendo.data.DataSource({ transport: { read: { url: crudServiceBaseUrl + "/detailproducts", dataType: "jsonp" }, update: { url: crudServiceBaseUrl + "/detailproducts/Update", dataType: "jsonp" }, destroy: { url: crudServiceBaseUrl + "/detailproducts/Destroy", dataType: "jsonp" }, parameterMap: function (options, operation) { if (operation !== "read" && options.models) { return { models: kendo.stringify(options.models) }; } } }, batch: true, pageSize: 20, autoSync: true, aggregate: [{ field: "TotalSales", aggregate: "sum" }], group: { field: "Category.CategoryName", dir: "desc", aggregates: [ { field: "TotalSales", aggregate: "sum" } ] }, schema: { model: { id: "ProductID", fields: { ProductID: { editable: false, nullable: true }, Discontinued: { type: "boolean", editable: false }, TotalSales: { type: "number", editable: false }, TargetSales: { type: "number", editable: false }, LastSupply: { type: "date" }, UnitPrice: { type: "number" }, UnitsInStock: { type: "number" }, Category: { defaultValue: { CategoryID: 8, CategoryName: "Seafood" } }, Country: { defaultValue: { CountryNameLong: "Bulgaria", CountryNameShort: "bg" } } } } } }); $("#grid").kendoGrid({ dataSource: dataSource, columnMenu: { filterable: false }, // 移除固定height,改用窗口滚动 scrollable: true, editable: "incell", pageable: true, sortable: true, navigatable: true, resizable: true, reorderable: true, groupable: true, filterable: true, dataBound: onDataBound, toolbar: ["excel", "pdf", "search"], columns: [{ selectable: true, width: 75, attributes: { "class": "checkbox-align", }, headerAttributes: { "class": "checkbox-align", } }, { field: "ProductName", title: "Product Name", template: "<div class='product-photo' style='background-image: url(../content/web/foods/#:data.ProductID#.jpg);'></div><div class='product-name'>#: ProductName #</div>", width: 300 }, { field: "UnitPrice", title: "Price", format: "{0:c}", width: 105 }, { field: "Discontinued", title: "In Stock", template: "<span id='badge_#=ProductID#' class='badgeTemplate'></span>", width: 130, }, { field: "Category.CategoryName", title: "Category", editor: clientCategoryEditor, groupHeaderTemplate: "Category: #=data.value#, Total Sales: #=kendo.format('{0:c}', aggregates.TotalSales.sum)#", width: 125 }, { field: "CustomerRating", title: "Rating", template: "<input id='rating_#=ProductID#' data-bind='value: CustomerRating' class='rating'/>", editable: returnFalse, width: 200 }, { field: "Country.CountryNameLong", title: "Country", template: "<div class='k-text-center'><img src='../content/web/country-flags/#:data.Country.CountryNameShort#.png' alt='Kendo UI for jQuery Grid #: data.Country.CountryNameLong# Flag' title='#: data.Country.CountryNameLong#' width='30' /></div>", editor: clientCountryEditor, width: 120 }, { field: "UnitsInStock", title: "Units", width: 105 }, { field: "TotalSales", title: "Total Sales", format: "{0:c}", width: 140, aggregates: ["sum"], }, { field: "TargetSales", title: "Target Sales", format: "{0:c}", template: "<span id='chart_#= ProductID#' class='sparkline-chart'></span>", width: 220 }, { command: "destroy", title: " ", width: 120 }], }); // 添加顶部滚动条 var grid = $("#grid").data("kendoGrid"); var topScroll = $("<div class='k-grid-scrollbar-top'></div>").insertBefore(grid.wrapper); var bottomScroll = grid.wrapper.find(".k-grid-content"); topScroll.scroll(function() { bottomScroll.scrollLeft($(this).scrollLeft()); }); bottomScroll.scroll(function() { topScroll.scrollLeft($(this).scrollLeft()); }); topScroll.css({ overflowX: "scroll", overflowY: "hidden", height: "18px", width: bottomScroll.width() }); topScroll.append($("<div></div>").css({ width: bottomScroll[0].scrollWidth, height: 1 })); }); function onDataBound(e) { var grid = this; // 同步表头与内容列宽 grid.thead.find("th").each(function(index) { var contentWidth = grid.tbody.find("td").eq(index).width(); $(this).width(contentWidth); }); grid.table.find("tr").each(function () { var dataItem = grid.dataItem(this); var themeColor = dataItem.Discontinued ? 'success' : 'error'; var text = dataItem.Discontinued ? 'available' : 'not available'; $(this).find(".badgeTemplate").kendoBadge({ themeColor: themeColor, text: text, }); $(this).find(".rating").kendoRating({ min: 1, max: 5, label: false, selection: "continuous" }); $(this).find(".sparkline-chart").kendoSparkline({ legend: { visible: false }, data: [dataItem.TargetSales], type: "bar", chartArea: { margin: 0, width: 180, background: "transparent" }, seriesDefaults: { labels: { visible: true, format: '{0}%', background: 'none' } }, categoryAxis: { majorGridLines: { visible: false }, majorTicks: { visible: false } }, valueAxis: { type: "numeric", min: 0, max: 130, visible: false, labels: { visible: false }, minorTicks: { visible: false }, majorGridLines: { visible: false } }, tooltip: { visible: false } }); kendo.bind($(this), dataItem); }); } function returnFalse() { return false; } function clientCategoryEditor(container, options) { $('<input required name="Category">') .appendTo(container) .kendoDropDownList({ autoBind: false, dataTextField: "CategoryName", dataValueField: "CategoryID", dataSource: { data: categories } }); } function clientCountryEditor(container, options) { $('<input required name="Country">') .appendTo(container) .kendoDropDownList({ dataTextField: "CountryNameLong", dataValueField: "CountryNameShort", template: "<div class='dropdown-country-wrap'><img src='../content/web/country-flags/#:CountryNameShort#.png' alt='Kendo UI for jQuery Grid #: CountryNameLong# Flag' title='#: CountryNameLong#' width='30' /><span>#:CountryNameLong #</span></div>", dataSource: { transport: { read: { url: " https://demos.telerik.com/kendo-ui/service/countries", dataType: "jsonp" } } }, autoWidth: true }); } var categories = [{ "CategoryID": 1, "CategoryName": "Beverages" }, { "CategoryID": 2, "CategoryName": "Condiments" }, { "CategoryID": 3, "CategoryName": "Confections" }, { "CategoryID": 4, "CategoryName": "Dairy Products" }, { "CategoryID": 5, "CategoryName": "Grains/Cereals" }, { "CategoryID": 6, "CategoryName": "Meat/Poultry" }, { "CategoryID": 7, "CategoryName": "Produce" }, { "CategoryID": 8, "CategoryName": "Seafood" }]; </script> <style type="text/css"> .customer-photo { display: inline-block; width: 32px; height: 32px; border-radius: 50%; background-size: 32px 35px; background-position: center center; vertical-align: middle; line-height: 32px; box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2); margin-left: 5px; } .customer-name { display: inline-block; vertical-align: middle; line-height: 32px; padding-left: 3px; } .k-grid tr .checkbox-align { text-align: center; vertical-align: middle; } .product-photo { display: inline-block; width: 32px; height: 32px; border-radius: 50%; background-size: 32px 35px; background-position: center center; vertical-align: middle; line-height: 32px; box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2); margin-right: 5px; } .product-name { display: inline-block; vertical-align: middle; line-height: 32px; padding-left: 3px; } .k-rating-container .k-rating-item { padding: 4px 0; } .k-rating-container .k-rating-item .k-icon { font-size: 16px; } .dropdown-country-wrap { display: flex; flex-wrap: nowrap; align-items: center; white-space: nowrap; } .dropdown-country-wrap img { margin-right: 10px; } #grid .k-grid-edit-row > td > .k-rating { margin-left: 0; width: 100%; } /* 固定表头样式 */ .k-grid-header { position: sticky; top: 0; z-index: 100; background-color: #fff; } /* 顶部滚动条样式 */ .k-grid-scrollbar-top { margin-bottom: -1px; border-bottom: 1px solid #ddd; } </style> </div> </body> </html>
内容的提问来源于stack exchange,提问作者Kunal Vijan
相关产品推荐
相关产品推荐

