图片“瀑布流”显示效果的实现
项目文件
项目文件在我的码云私库:https://gitee.com/huoyingwhw/OldSite
效果如下
实现的思路
1:后台的数据是用DRF从数据库中拿到并且构建成相应的格式——DRF拿到存储展示图片的数据库的表的所有对象,用自己的序列化器以及分页器在后端构建数据结构;
2:前端用JS代码实现,用ajax向后段DRF请求数据,拿到请求的数据后在前端实现“瀑布流”的信息展示效果。
实现的过程详述
一定要记得注册
本功能用到了rest_framework与django-filter,需要在settings的INSTALLED_APPS中注册一下:
INSTALLED_APPS = [ xxxxxx 'rest_framework', 'django_filters', ]
Model
本功能只设计图片的展示,因此只设计一个存储图片数据以及图片分类的Model。
class FeedBack(models.Model): type_choices = ( (0,'未分类'), (1,'学习笔记'), (2,'学员评价'), (3,'入职邀约'), ) img = models.ImageField(verbose_name='图片',upload_to='img/feedback/') back_type = models.IntegerField(choices=type_choices,verbose_name='分类',default=0)
注意:
(1)图片用ImageField类型存储;
(2)需要先进行Django的media配置(见这篇博客),upload_to要写“相对路径”!图片实际存放的“根”路径是项目的media文件夹(如果media配置是这么配的话),存放在:media/img/feedback/目录下。
(3)back_type用choices,表示文章分类——体现在前端就是:不同的选项卡显示不同类型的图片。
路由配置
这部分功能的路由配置:
# 学习笔记、学员评价、入职邀约用一个路由去处理——这三个功能放在选项卡中展示~ url(r'^(learning-notes|student-evaluation|work-invitation)\.html/$',feedback.feedback_list,name='feedback'), # ajax_feedback————DRF构建数据与返回数据的路由 url(r'^ajax_feedback/$', feedback.AjaxFeedback.as_view(), name='ajax_feedback'),
(1)主要用两条路由:一个是展示页面的路由——注意这里用分组的方式匹配三个不同的路由:因为前端需要展示三个不同的选项卡;
另外一条路由是DRF构建数据返回数据的路由——后面ajax向后端请求数据的路由就是它!
(2)将两部分的视图函数写在了同一个py文件中:feedback.py。
(3)由上面可知:三个选项卡的视图是FBV模式,DRF的视图是CBV模式的。
视图函数以及DRF序列化器与分页器的配置
这部分功能的视图函数如下——feedback.py:
# -*- coding:utf-8 -*- from django.shortcuts import render from rest_framework import generics from django_filters.rest_framework import DjangoFilterBackend from repository import models from web.serializers import FeedbackSerializer from web.pagination import DefaultPagination # 利用table参数去区分不同的页面
# table的值就是下面的字典 feedback_dict 的key中的一个值~~
def feedback_list(request,table): feedback_dict = { 'learning-notes': {'banner': 'images/classmate-say/classmate-banner1.jpg', 'title': '学习笔记'}, 'student-evaluation': {'banner': 'images/classmate-say/classmate-banner2.jpg', 'title': '学员评价'}, 'work-invitation': {'banner': 'images/classmate-say/classmate-banner3.jpg', 'title': '入职邀约'}, } return render(request,'oldboy/pages/classmate-say/feedback.html', {'table':table,'banner':feedback_dict[table]['banner'],'title':feedback_dict[table]['title']}) # feedback————DRF的视图 class AjaxFeedback(generics.ListAPIView): queryset = models.FeedBack.objects.all() serializer_class = FeedbackSerializer pagination_class = DefaultPagination filter_backends = [DjangoFilterBackend,] filter_fields = ['back_type']
视图函数与路由对应,第一个是返回三个选项卡页面的视图,另外一个是DRF的CBV模式的视图函数——注意它的写法,我们在此之前还写了DRF的一个序列化的类与一个分页类~
序列化类—serializers.py文件中的FeedbackSerializer类(也可以创建一个serializers目录,把类写在里面的__init__.py文件中)
# -*- coding:utf-8 -*- from rest_framework import serializers from repository import models class FeedbackSerializer(serializers.ModelSerializer): class Meta: model = models.FeedBack fields = ['img']
分页类—pagination.py文件中的DefaultPagination类(也可以创建一个pagination目录,把类写在里面的__init__.py文件中)
# -*- coding:utf-8 -*- from rest_framework.pagination import PageNumberPagination class DefaultPagination(PageNumberPagination): page_size = 8 # 一页多少条数据 page_query_param = 'page' # 分页查询条件的key page_size_query_param = 'size' # max_page_size = 8
——之所以创建这个分页类,其实还是为了实现“瀑布流”的效果:前端向后台请求数据的时候,后台“一页一页的把数据传给前端”,前端先显示一页的数据,然后在记录一下页面滑轮滚动的位置,到达页面底端某个位置后再次向后台请求下一页的数据~~以此类推,直到后台把所有的数据都传完为止。
feedback.html文件内容如下
{% extends 'base.html' %} {% load static %} {% block css %} <link rel="stylesheet" href="{% static 'classmate-say/learning-notes.css' %}"> <!-- 点击图片放大--> <link rel="stylesheet" href="{% static 'oldboy/styles/magnific-popup.css' %}"> <!-- 瀑布流css --> <link rel="stylesheet" href="{% static 'oldboy/styles/salvattore.css' %}"> {% endblock %} {% block main %} <div class="main"> <div class="banner"> {# 用传过来的banner值找对应的图片 #} <img src="{% static banner %}" alt=""> </div> <ul class="classmate-nav"> {# 给每个选项卡的a标签加上请求的路径与参数 #} <a href="{% url 'web:feedback' 'learning-notes' %}"> {# 判断一下:如果传过来的table值是当前的标签的值,就加上 thisli 的效果 #} <li class="{% if table == 'learning-notes' %}thisli{% endif %}" back_type="1"> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon1.jpg' %}" alt=""> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon1-1.jpg' %}" alt=""> <span>学习笔记</span> </li> </a> {# 给每个选项卡的a标签加上请求的路径与参数 #} <a href="{% url 'web:feedback' 'student-evaluation' %}"> {# 判断一下:如果传过来的table值是当前的标签的值,就加上 thisli 的效果 #} <li class="{% if table == 'student-evaluation' %}thisli{% endif %}" back_type="2"> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon2.jpg' %}" alt=""> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon2-1.jpg' %}" alt=""> <span>学员评价</span> </li> </a> {# 给每个选项卡的a标签加上请求的路径与参数 #} <a href="{% url 'web:feedback' 'work-invitation' %}"> {# 判断一下:如果传过来的table值是当前的标签的值,就加上 thisli 的效果 #} <li class="{% if table == 'work-invitation' %}thisli{% endif %}" back_type="3"> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon3.jpg' %}" alt=""> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon3-1.jpg' %}" alt=""> <span>提醒</span> </li> </a> <a href=""> <li> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon4.jpg' %}" alt=""> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon4-1.jpg' %}" alt=""> <span>同学说 <i>(99+)</i></span> </li> </a> <a href=""> <li> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon5.jpg' %}" alt=""> <img src="{% static 'oldboy/images/classmate-say/classmate-say-icon5-1.jpg' %}" alt=""> <span>专访</span> </li> </a> </ul> <div id="fh5co-main"> <div class="container"> <div class="row"> <div id="fh5co-board" data-columns> {# 这是图片展示写死的部分~~仅用于测试 #} {# <div class="item">#} {# <div class="animate-box">#} {# <a href="{% get_media_prefix %}img/feedback/img_1.jpg"#} {# class="image-popup fh5co-board-img"><img#} {# src="{% get_media_prefix %}img/feedback/img_1.jpg"#} {# alt="Free HTML5 Bootstrap template"></a>#} {# </div>#} {# </div>#} {# <div class="item">#} {# <div class="animate-box">#} {# <a href="{% get_media_prefix %}img/feedback/img_1.jpg"#} {# class="image-popup fh5co-board-img"><img#} {# src="{% get_media_prefix %}img/feedback/img_1.jpg"#} {# alt="Free HTML5 Bootstrap template"></a>#} {# </div>#} {# </div>#} </div> </div> </div> </div> </div> {% endblock %} {% block js %} <!-- 滚动监听插件 --> <script src="{% static 'oldboy/plugins/jquery.waypoints.min.js' %}"></script> <!-- 点击图片放大js --> <script src="{% static 'oldboy/plugins/jquery.magnific-popup.min.js' %}"></script> <!-- 瀑布流自适应 --> <script src="{% static 'oldboy/plugins/salvattore.min.js' %}"></script> <!-- 瀑布流js --> <script src="{% static 'oldboy/plugins/main.js' %}"></script> <!-- 首页动效 实现瀑布流的代码 --> <script src="{% static 'classmate-say/learning-notes.js' %}"></script> {% endblock %}
瀑布流的js代码主要在learning-nites.js中
里面的代码如下:
$(function () { var back_type = $('.thisli').attr('back_type'); var page = 1; var num = 0; var next = 1; var tag = 1; function get_info() { if (next && tag) { tag = 0; // 发送ajax请求~~ $.ajax({
//请求的路径加上参数~用ES6语法,可以加变量 url: `/web/ajax_feedback/?back_type=${back_type}&page=${page}`, success: function (data) { // console.log(data) var results = data.results; next = data.next; for (var i in results) { var row = results[i]; var col = $('.column')[num % 4]; var item = ` <div class="item"> <div class="animate-box"> <a href="${row.img}" class="image-popup fh5co-board-img"><img src="${row.img}" alt="Free HTML5 Bootstrap template"></a> </div> </div>`; $(col).append(item); // 调用main.js中的两个函数!!! magnifPopup(); animateBoxWayPoint(); num += 1 } page += 1; tag = 1; } }) } } get_info(); //图片分批加载 $(window).scroll(function () { //$(document).scrollTop() 滚动条位置距页面顶部的距离; //$(document).height() 整个页面的总高度; //$(window).height() 当前窗口的高度; //判断是否已经滚动到页面底部; if ($(document).scrollTop() >= $(document).height() - $(window).height() - 180) { get_info(); } // alert(1111) }); }());
main.js的内容如下
由于实现瀑布流的代码中用到了main.js的两个函数,这里也把main.js的内容发一下:
// Magnific Popup var magnifPopup = function () { $('.image-popup').magnificPopup({ type: 'image', removalDelay: 300, mainClass: 'mfp-with-zoom', titleSrc: 'title', gallery: { enabled: true }, zoom: { enabled: true, // By default it's false, so don't forget to enable it duration: 300, // duration of the effect, in milliseconds easing: 'ease-in-out', // CSS transition easing function // The "opener" function should return the element from which popup will be zoomed in // and to which popup will be scaled down // By defailt it looks for an image tag: opener: function (openerElement) { // openerElement is the element on which popup was initialized, in this case its <a> tag // you don't need to add "opener" option if this code matches your needs, it's defailt one. return openerElement.is('img') ? openerElement : openerElement.find('img'); } } }); };
var animateBoxWayPoint = function () { if ($('.animate-box').length > 0) { $('.animate-box').waypoint(function (direction) { if (direction === 'down' && !$(this).hasClass('animated')) { $(this.element).stop().animate({ opacity: 1 }) } }, {offset: '75%'}); } };
(function () { 'use strict'; // iPad and iPod detection var isiPad = function () { return (navigator.platform.indexOf("iPad") != -1); }; var isiPhone = function () { return ( (navigator.platform.indexOf("iPhone") != -1) || (navigator.platform.indexOf("iPod") != -1) ); }; // OffCanvass var offCanvass = function () { $('body').on('click', '.js-fh5co-menu-btn, .js-fh5co-offcanvass-close', function () { $('#fh5co-offcanvass').toggleClass('fh5co-awake'); }); }; // Click outside of offcanvass var mobileMenuOutsideClick = function () { $(document).click(function (e) { var container = $("#fh5co-offcanvass, .js-fh5co-menu-btn"); if (!container.is(e.target) && container.has(e.target).length === 0) { if ($('#fh5co-offcanvass').hasClass('fh5co-awake')) { $('#fh5co-offcanvass').removeClass('fh5co-awake'); } } }); $(window).scroll(function () { if ($(window).scrollTop() > 500) { if ($('#fh5co-offcanvass').hasClass('fh5co-awake')) { $('#fh5co-offcanvass').removeClass('fh5co-awake'); } } }); }; $(function () { magnifPopup(); offCanvass(); mobileMenuOutsideClick(); animateBoxWayPoint(); }); }());