├── db.sqlite3 ├── static_files ├── img │ ├── logo.png │ └── youtube.png ├── css │ └── main.css └── js │ ├── app.js │ └── particles.min.js ├── requirements.txt ├── YouTubeDownloader ├── forms.py ├── urls.py ├── wsgi.py ├── views.py └── settings.py ├── vercel.json ├── manage.py ├── LICENSE ├── templates ├── base.html └── home.html └── readme.md /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hi-im-gabriel/django-ytdownloader/HEAD/db.sqlite3 -------------------------------------------------------------------------------- /static_files/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hi-im-gabriel/django-ytdownloader/HEAD/static_files/img/logo.png -------------------------------------------------------------------------------- /static_files/img/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hi-im-gabriel/django-ytdownloader/HEAD/static_files/img/youtube.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django>=3.2.9 2 | django-crispy-forms>=1.13.0 3 | pytz>=2021.3 4 | pytube==11.0.2 5 | requests==2.26.0 6 | whitenoise>=5.3.0 -------------------------------------------------------------------------------- /YouTubeDownloader/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class DownloadForm(forms.Form): 4 | url = forms.CharField(widget=forms.TextInput(attrs={ 'placeholder': 'Enter video url' }), label=False) -------------------------------------------------------------------------------- /YouTubeDownloader/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import RedirectView 3 | from .views import download_video 4 | 5 | 6 | urlpatterns = [ 7 | path('', download_video), 8 | path('', RedirectView.as_view(url='/')) # Exceptions handling 9 | ] 10 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [{ 3 | "src": "YouTubeDownloader/wsgi.py", 4 | "use": "@ardnt/vercel-python-wsgi", 5 | "config": { "maxLambdaSize": "15mb" } 6 | }], 7 | "routes": [ 8 | { 9 | "src": "/(.*)", 10 | "dest": "YouTubeDownloader/wsgi.py" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /YouTubeDownloader/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for YTVDownloader project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'YouTubeDownloader.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /static_files/css/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin-top: 40px; 5 | background-color: #181818; 6 | color: #ffffff; 7 | } 8 | 9 | canvas { 10 | display: block; 11 | } 12 | 13 | #particles-js { 14 | background-color: #181818; 15 | position: fixed; 16 | top: 0; 17 | right: 0; 18 | bottom: 0; 19 | left: 0; 20 | z-index: 0; 21 | } 22 | 23 | ::-webkit-scrollbar { 24 | display: none; 25 | } 26 | 27 | ul { 28 | list-style: none; 29 | } -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'YouTubeDownloader.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gabriel Tavares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | YouTube Downloader 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | {% block content %} {% endblock %} 25 | 26 |
27 |
28 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

(DEPRECATED)YouTube Downloader
Updated version: https://github.com/gabzin/downtube-nextjs

2 |

3 | 4 | 5 | 6 | 7 | 8 | 9 |

⭐️ Star this project ⭐️

10 |

11 |

https://downtube.vercel.app/

12 |

13 | 14 |

15 | 16 | # Updates 17 | > # v2021.12 18 | > 19 | > #### Added: 20 | > 21 | > - YouTube API to get likes/favs count 22 | > - Visual enhancement 23 | > - Particles JS 24 | > - Bootstrap icons 25 | > 26 | > #### Bug fixes: 27 | > 28 | > - Opening video/audio links instead of downloading 29 | > 30 | > # v2021.11 31 | > 32 | > #### Added: 33 | > 34 | > - Migration from pafy/youtube-dl to pytube (Performance reasons) 35 | > - Visual enhancement 36 | > 37 | > #### Bug fixes: 38 | > 39 | > - Error 404 handling 40 | > 41 | > #### Future updates: 42 | > 43 | > - 1080p60+ downloads 44 | > - Dark/Light mode option 45 | 46 | 47 | # Requirements 48 | - Python3 49 | - Pip3 50 | - Git 51 | - YouTube V3 API KEY (otherwise, likes count aren't avalible with pytube anymore): 52 | 53 | https://developers.google.com/youtube/v3 54 | 55 |

56 | 57 |

58 | 59 | 60 | # Installation 61 | 62 | ```bash 63 | git clone https://github.com/gabzin/django-ytdownloader 64 | cd django-ytdownloader 65 | pip3 install -r requirements.txt 66 | ``` 67 | 68 | # Starting server 69 | 70 | ```bash 71 | python manage.py runserver 72 | ``` 73 | -------------------------------------------------------------------------------- /static_files/js/app.js: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------- 2 | /* How to use? : Check the GitHub README 3 | /* ----------------------------------------------- */ 4 | 5 | /* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */ 6 | /* 7 | particlesJS.load('particles-js', 'particles.json', function() { 8 | console.log('particles.js loaded - callback'); 9 | }); 10 | */ 11 | 12 | /* Otherwise just put the config content (json): */ 13 | 14 | particlesJS('particles-js', 15 | 16 | { 17 | "particles": { 18 | "number": { 19 | "value": 80, 20 | "density": { 21 | "enable": true, 22 | "value_area": 800 23 | } 24 | }, 25 | "color": { 26 | "value": "#ffffff" 27 | }, 28 | "shape": { 29 | "type": "circle", 30 | "stroke": { 31 | "width": 0, 32 | "color": "#000000" 33 | }, 34 | "polygon": { 35 | "nb_sides": 0 36 | }, 37 | "image": { 38 | "src": "img/github.svg", 39 | "width": 100, 40 | "height": 100 41 | } 42 | }, 43 | "opacity": { 44 | "value": 1, 45 | "random": false, 46 | "anim": { 47 | "enable": false, 48 | "speed": 1, 49 | "opacity_min": 0.1, 50 | "sync": false 51 | } 52 | }, 53 | "size": { 54 | "value": 0, 55 | "random": true, 56 | "anim": { 57 | "enable": false, 58 | "speed": 40, 59 | "size_min": 0.1, 60 | "sync": false 61 | } 62 | }, 63 | "line_linked": { 64 | "enable": true, 65 | "distance": 150, 66 | "color": "#ffffff", 67 | "opacity": 0.4, 68 | "width": 1 69 | }, 70 | "move": { 71 | "enable": true, 72 | "speed": 6, 73 | "direction": "none", 74 | "random": false, 75 | "straight": false, 76 | "out_mode": "out", 77 | "attract": { 78 | "enable": false, 79 | "rotateX": 600, 80 | "rotateY": 1200 81 | } 82 | } 83 | }, 84 | "interactivity": { 85 | "detect_on": "canvas", 86 | "events": { 87 | "onhover": { 88 | "enable": false, 89 | "mode": "repulse" 90 | }, 91 | "onclick": { 92 | "enable": false, 93 | "mode": "push" 94 | }, 95 | "resize": true 96 | }, 97 | "modes": { 98 | "grab": { 99 | "distance": 400, 100 | "line_linked": { 101 | "opacity": 1 102 | } 103 | }, 104 | "bubble": { 105 | "distance": 400, 106 | "size": 40, 107 | "duration": 2, 108 | "opacity": 8, 109 | "speed": 3 110 | }, 111 | "repulse": { 112 | "distance": 200 113 | }, 114 | "push": { 115 | "particles_nb": 4 116 | }, 117 | "remove": { 118 | "particles_nb": 2 119 | } 120 | } 121 | }, 122 | "retina_detect": true, 123 | "config_demo": { 124 | "hide_card": false, 125 | "background_color": "#b61924", 126 | "background_image": "", 127 | "background_position": "50% 50%", 128 | "background_repeat": "no-repeat", 129 | "background_size": "cover" 130 | } 131 | } 132 | 133 | ); -------------------------------------------------------------------------------- /YouTubeDownloader/views.py: -------------------------------------------------------------------------------- 1 | #Imports 2 | from django.http.response import HttpResponse 3 | from django.shortcuts import render 4 | from django.contrib import messages 5 | from .forms import DownloadForm 6 | from pytube import YouTube 7 | from math import pow, floor, log 8 | from datetime import timedelta 9 | from requests import get 10 | 11 | # Your YouTube V3 Api Key 12 | KEY = "" 13 | 14 | # Convert from bytes 15 | def convertsize(size_bytes): 16 | if size_bytes == 0: 17 | return "0B" 18 | size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") 19 | i = int(floor(log(size_bytes, 1024))) 20 | p = pow(1024, i) 21 | s = round(size_bytes / p, 2) 22 | return "%s %s" % (s, size_name[i]) 23 | 24 | # Convert long numbers 25 | def humanformat(number): 26 | units = ['', 'K', 'M', 'B', 'T', 'Q'] 27 | k = 1000.0 28 | magnitude = int(floor(log(number, k))) 29 | return '%.2f%s' % (number / k**magnitude, units[magnitude]) 30 | 31 | #When click search button 32 | def download_video(request, string=""): 33 | global video_url 34 | form = DownloadForm(request.POST or None) 35 | if form.is_valid(): 36 | video_url = form.cleaned_data.get("url") 37 | try: 38 | yt_obj = YouTube(video_url) 39 | videos = yt_obj.streams.filter(is_dash=False).desc() 40 | audios = yt_obj.streams.filter(only_audio=True).order_by('abr').desc() 41 | except Exception as e: 42 | #messages.error(request, 'Invalid URL.') 43 | messages.error(request, e) 44 | return render(request, 'home.html',{ 'form': form }) 45 | 46 | video_audio_streams = [] 47 | audio_streams = [] 48 | try: 49 | url = f"https://www.googleapis.com/youtube/v3/videos?id={yt_obj.video_id}&key={KEY}&part=statistics" 50 | video_stats = get(url).json() 51 | video_likes = video_stats['items'][0]['statistics']['likeCount'] 52 | video_favs = video_stats['items'][0]['statistics']['favoriteCount'] 53 | except: 54 | video_likes = 0 55 | # List of video streams dictionaries 56 | for s in videos: 57 | video_audio_streams.append({ 58 | 'resolution' : s.resolution, 59 | 'extension' : s.mime_type.replace('video/',''), 60 | 'file_size' : convertsize(s.filesize), 61 | 'video_url' : s.url, 'file_name' : yt_obj.title + '.' + s.mime_type.replace('video/','') 62 | }) 63 | 64 | # List of audio streams dictionaries 65 | for s in audios: 66 | audio_streams.append({ 67 | 'resolution' : s.abr, 68 | 'extension' : s.mime_type.replace('audio/',''), 69 | 'file_size' : convertsize(s.filesize), 70 | 'video_url' : s.url, 'file_name' : yt_obj.title + '.' + s.mime_type.replace('video/','') 71 | }) 72 | 73 | if yt_obj.rating == None: 74 | rating = 5 75 | else: 76 | rating = yt_obj.rating 77 | 78 | # Full content to render 79 | context = { 80 | 'form' : form,'title' : yt_obj.title, 81 | 'rating': humanformat(int(video_likes)), 82 | 'thumb' : yt_obj.thumbnail_url, 'author' : yt_obj.author, 83 | 'author_url' : yt_obj.channel_url, 84 | 'duration' : str(timedelta(seconds=yt_obj.length)), 'views' : humanformat(yt_obj.views) if yt_obj.views >= 1000 else yt_obj.views, 85 | 'stream_audio' : audio_streams, 'streams' : video_audio_streams 86 | } 87 | 88 | return render(request, 'home.html', context) 89 | 90 | return render(request, 'home.html',{ 'form': form }) -------------------------------------------------------------------------------- /YouTubeDownloader/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for YouTubeDownloader project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.1.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.1/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = ';ycuh^rx/b(]]/?!6za.833=yu$d~4,[rzh$febpvy6c6=sjrr' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | ALLOWED_HOSTS = ['*'] #.vercel.app 28 | 29 | # Application definition 30 | 31 | INSTALLED_APPS = [ 32 | 'django.contrib.auth', 33 | 'django.contrib.contenttypes', 34 | 'django.contrib.sessions', 35 | 'django.contrib.messages', 36 | 'django.contrib.staticfiles', 37 | 'crispy_forms', 38 | ] 39 | 40 | MIDDLEWARE = [ 41 | 'django.middleware.security.SecurityMiddleware', 42 | 'django.contrib.sessions.middleware.SessionMiddleware', 43 | 'django.middleware.common.CommonMiddleware', 44 | 'django.middleware.csrf.CsrfViewMiddleware', 45 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 46 | 'django.contrib.messages.middleware.MessageMiddleware', 47 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 48 | ] 49 | 50 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedStaticFilesStorage' 51 | 52 | ROOT_URLCONF = 'YouTubeDownloader.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [os.path.join(BASE_DIR, 'templates')] 58 | , 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'YouTubeDownloader.wsgi.application' 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases 75 | 76 | DATABASES = {} 77 | 78 | # Password validation 79 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators 80 | 81 | AUTH_PASSWORD_VALIDATORS = [ 82 | { 83 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 84 | }, 85 | { 86 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 87 | }, 88 | { 89 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 90 | }, 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 93 | }, 94 | ] 95 | 96 | # Internationalization 97 | # https://docs.djangoproject.com/en/2.1/topics/i18n/ 98 | 99 | LANGUAGE_CODE = 'pt-br' 100 | 101 | TIME_ZONE = 'UTC' 102 | 103 | USE_I18N = True 104 | 105 | USE_L10N = True 106 | 107 | USE_TZ = True 108 | 109 | # Static files (CSS, JavaScript, Images) 110 | # https://docs.djangoproject.com/en/2.1/howto/static-files/ 111 | 112 | 113 | STATIC_URL = '/static/' 114 | STATIC_ROOT = os.path.join(BASE_DIR, 'static_root') 115 | 116 | STATICFILES_DIRS = [os.path.join(BASE_DIR, "static_files")] 117 | 118 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 119 | -------------------------------------------------------------------------------- /templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} {% load crispy_forms_tags %} {% load static %} {% block content %} 2 | 3 |
4 |
5 |

6 | 7 |

8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 | {% csrf_token %} {{ form|crispy }} 16 | 17 |
18 | 19 | {% if messages %} 20 |
    21 | {% for message in messages %} 22 | {{ message }} 23 | {% endfor %} 24 |
25 | {% endif %} 26 |
27 |
28 | 29 | 30 | 35 | 36 | {% if title %} 37 |
38 |
39 |

{{ author }}

40 |

{{ title }}

41 |
42 |
43 |
44 |
45 | 46 |
47 | 48 |
49 |
    50 |
  • 51 |

    : {{ duration }}

    52 |
  • 53 |
  • 54 |

    : {{ views }}

    55 |
  • 56 |
  • 57 |

    : {{ rating }}

    58 |
  • 59 |
60 |
61 |
62 | 63 |
64 |
65 |

Video

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {% for stream in streams %} 77 | 78 | {% if stream.resolution == '720p' %} 79 | 80 | {% else %} 81 | 82 | {% endif %} 83 | 84 | 85 | 88 | 89 | {% endfor %} 90 | 91 |
{{ stream.resolution }} {{ stream.resolution }}{{ stream.file_size }}{{ stream.extension }} 86 | Download 87 |
92 | 93 |

Audio Only

94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | {% for stream in stream_audio %} 105 | 106 | 107 | 108 | 109 | 112 | 113 | {% endfor %} 114 | 115 |
{{ stream.resolution }}{{ stream.file_size }}{{ stream.extension }} 110 | Download 111 |
116 |
117 |
118 | {% endif %} 119 | {% endblock %} 120 | -------------------------------------------------------------------------------- /static_files/js/particles.min.js: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------- 2 | /* Author : Vincent Garreau - vincentgarreau.com 3 | /* MIT license: http://opensource.org/licenses/MIT 4 | /* Demo / Generator : vincentgarreau.com/particles.js 5 | /* GitHub : github.com/VincentGarreau/particles.js 6 | /* How to use? : Check the GitHub README 7 | /* v2.0.0 8 | /* ----------------------------------------------- */ 9 | function hexToRgb(e) { 10 | var a = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 11 | e = e.replace(a, function (e, a, t, i) { 12 | return a + a + t + t + i + i 13 | }); 14 | var t = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e); 15 | return t ? { 16 | r: parseInt(t[1], 16), 17 | g: parseInt(t[2], 16), 18 | b: parseInt(t[3], 16) 19 | } : null 20 | } 21 | 22 | function clamp(e, a, t) { 23 | return Math.min(Math.max(e, a), t) 24 | } 25 | 26 | function isInArray(e, a) { 27 | return a.indexOf(e) > -1 28 | } 29 | var pJS = function (e, a) { 30 | var t = document.querySelector("#" + e + " > .particles-js-canvas-el"); 31 | this.pJS = { 32 | canvas: { 33 | el: t, 34 | w: t.offsetWidth, 35 | h: t.offsetHeight 36 | }, 37 | particles: { 38 | number: { 39 | value: 400, 40 | density: { 41 | enable: !0, 42 | value_area: 800 43 | } 44 | }, 45 | color: { 46 | value: "#fff" 47 | }, 48 | shape: { 49 | type: "circle", 50 | stroke: { 51 | width: 0, 52 | color: "#ff0000" 53 | }, 54 | polygon: { 55 | nb_sides: 5 56 | }, 57 | image: { 58 | src: "", 59 | width: 100, 60 | height: 100 61 | } 62 | }, 63 | opacity: { 64 | value: 1, 65 | random: !1, 66 | anim: { 67 | enable: !1, 68 | speed: 2, 69 | opacity_min: 0, 70 | sync: !1 71 | } 72 | }, 73 | size: { 74 | value: 20, 75 | random: !1, 76 | anim: { 77 | enable: !1, 78 | speed: 20, 79 | size_min: 0, 80 | sync: !1 81 | } 82 | }, 83 | line_linked: { 84 | enable: !0, 85 | distance: 100, 86 | color: "#fff", 87 | opacity: 1, 88 | width: 1 89 | }, 90 | move: { 91 | enable: !0, 92 | speed: 2, 93 | direction: "none", 94 | random: !1, 95 | straight: !1, 96 | out_mode: "out", 97 | bounce: !1, 98 | attract: { 99 | enable: !1, 100 | rotateX: 3e3, 101 | rotateY: 3e3 102 | } 103 | }, 104 | array: [] 105 | }, 106 | interactivity: { 107 | detect_on: "canvas", 108 | events: { 109 | onhover: { 110 | enable: !0, 111 | mode: "grab" 112 | }, 113 | onclick: { 114 | enable: !0, 115 | mode: "push" 116 | }, 117 | resize: !0 118 | }, 119 | modes: { 120 | grab: { 121 | distance: 100, 122 | line_linked: { 123 | opacity: 1 124 | } 125 | }, 126 | bubble: { 127 | distance: 200, 128 | size: 80, 129 | duration: .4 130 | }, 131 | repulse: { 132 | distance: 200, 133 | duration: .4 134 | }, 135 | push: { 136 | particles_nb: 4 137 | }, 138 | remove: { 139 | particles_nb: 2 140 | } 141 | }, 142 | mouse: {} 143 | }, 144 | retina_detect: !1, 145 | fn: { 146 | interact: {}, 147 | modes: {}, 148 | vendors: {} 149 | }, 150 | tmp: {} 151 | }; 152 | var i = this.pJS; 153 | a && Object.deepExtend(i, a), i.tmp.obj = { 154 | size_value: i.particles.size.value, 155 | size_anim_speed: i.particles.size.anim.speed, 156 | move_speed: i.particles.move.speed, 157 | line_linked_distance: i.particles.line_linked.distance, 158 | line_linked_width: i.particles.line_linked.width, 159 | mode_grab_distance: i.interactivity.modes.grab.distance, 160 | mode_bubble_distance: i.interactivity.modes.bubble.distance, 161 | mode_bubble_size: i.interactivity.modes.bubble.size, 162 | mode_repulse_distance: i.interactivity.modes.repulse.distance 163 | }, i.fn.retinaInit = function () { 164 | i.retina_detect && window.devicePixelRatio > 1 ? (i.canvas.pxratio = window.devicePixelRatio, i.tmp.retina = !0) : (i.canvas.pxratio = 1, i.tmp.retina = !1), i.canvas.w = i.canvas.el.offsetWidth * i.canvas.pxratio, i.canvas.h = i.canvas.el.offsetHeight * i.canvas.pxratio, i.particles.size.value = i.tmp.obj.size_value * i.canvas.pxratio, i.particles.size.anim.speed = i.tmp.obj.size_anim_speed * i.canvas.pxratio, i.particles.move.speed = i.tmp.obj.move_speed * i.canvas.pxratio, i.particles.line_linked.distance = i.tmp.obj.line_linked_distance * i.canvas.pxratio, i.interactivity.modes.grab.distance = i.tmp.obj.mode_grab_distance * i.canvas.pxratio, i.interactivity.modes.bubble.distance = i.tmp.obj.mode_bubble_distance * i.canvas.pxratio, i.particles.line_linked.width = i.tmp.obj.line_linked_width * i.canvas.pxratio, i.interactivity.modes.bubble.size = i.tmp.obj.mode_bubble_size * i.canvas.pxratio, i.interactivity.modes.repulse.distance = i.tmp.obj.mode_repulse_distance * i.canvas.pxratio 165 | }, i.fn.canvasInit = function () { 166 | i.canvas.ctx = i.canvas.el.getContext("2d") 167 | }, i.fn.canvasSize = function () { 168 | i.canvas.el.width = i.canvas.w, i.canvas.el.height = i.canvas.h, i && i.interactivity.events.resize && window.addEventListener("resize", function () { 169 | i.canvas.w = i.canvas.el.offsetWidth, i.canvas.h = i.canvas.el.offsetHeight, i.tmp.retina && (i.canvas.w *= i.canvas.pxratio, i.canvas.h *= i.canvas.pxratio), i.canvas.el.width = i.canvas.w, i.canvas.el.height = i.canvas.h, i.particles.move.enable || (i.fn.particlesEmpty(), i.fn.particlesCreate(), i.fn.particlesDraw(), i.fn.vendors.densityAutoParticles()), i.fn.vendors.densityAutoParticles() 170 | }) 171 | }, i.fn.canvasPaint = function () { 172 | i.canvas.ctx.fillRect(0, 0, i.canvas.w, i.canvas.h) 173 | }, i.fn.canvasClear = function () { 174 | i.canvas.ctx.clearRect(0, 0, i.canvas.w, i.canvas.h) 175 | }, i.fn.particle = function (e, a, t) { 176 | if (this.radius = (i.particles.size.random ? Math.random() : 1) * i.particles.size.value, i.particles.size.anim.enable && (this.size_status = !1, this.vs = i.particles.size.anim.speed / 100, i.particles.size.anim.sync || (this.vs = this.vs * Math.random())), this.x = t ? t.x : Math.random() * i.canvas.w, this.y = t ? t.y : Math.random() * i.canvas.h, this.x > i.canvas.w - 2 * this.radius ? this.x = this.x - this.radius : this.x < 2 * this.radius && (this.x = this.x + this.radius), this.y > i.canvas.h - 2 * this.radius ? this.y = this.y - this.radius : this.y < 2 * this.radius && (this.y = this.y + this.radius), i.particles.move.bounce && i.fn.vendors.checkOverlap(this, t), this.color = {}, "object" == typeof e.value) 177 | if (e.value instanceof Array) { 178 | var s = e.value[Math.floor(Math.random() * i.particles.color.value.length)]; 179 | this.color.rgb = hexToRgb(s) 180 | } else void 0 != e.value.r && void 0 != e.value.g && void 0 != e.value.b && (this.color.rgb = { 181 | r: e.value.r, 182 | g: e.value.g, 183 | b: e.value.b 184 | }), void 0 != e.value.h && void 0 != e.value.s && void 0 != e.value.l && (this.color.hsl = { 185 | h: e.value.h, 186 | s: e.value.s, 187 | l: e.value.l 188 | }); 189 | else "random" == e.value ? this.color.rgb = { 190 | r: Math.floor(256 * Math.random()) + 0, 191 | g: Math.floor(256 * Math.random()) + 0, 192 | b: Math.floor(256 * Math.random()) + 0 193 | } : "string" == typeof e.value && (this.color = e, this.color.rgb = hexToRgb(this.color.value)); 194 | this.opacity = (i.particles.opacity.random ? Math.random() : 1) * i.particles.opacity.value, i.particles.opacity.anim.enable && (this.opacity_status = !1, this.vo = i.particles.opacity.anim.speed / 100, i.particles.opacity.anim.sync || (this.vo = this.vo * Math.random())); 195 | var n = {}; 196 | switch (i.particles.move.direction) { 197 | case "top": 198 | n = { 199 | x: 0, 200 | y: -1 201 | }; 202 | break; 203 | case "top-right": 204 | n = { 205 | x: .5, 206 | y: -.5 207 | }; 208 | break; 209 | case "right": 210 | n = { 211 | x: 1, 212 | y: -0 213 | }; 214 | break; 215 | case "bottom-right": 216 | n = { 217 | x: .5, 218 | y: .5 219 | }; 220 | break; 221 | case "bottom": 222 | n = { 223 | x: 0, 224 | y: 1 225 | }; 226 | break; 227 | case "bottom-left": 228 | n = { 229 | x: -.5, 230 | y: 1 231 | }; 232 | break; 233 | case "left": 234 | n = { 235 | x: -1, 236 | y: 0 237 | }; 238 | break; 239 | case "top-left": 240 | n = { 241 | x: -.5, 242 | y: -.5 243 | }; 244 | break; 245 | default: 246 | n = { 247 | x: 0, 248 | y: 0 249 | } 250 | } 251 | i.particles.move.straight ? (this.vx = n.x, this.vy = n.y, i.particles.move.random && (this.vx = this.vx * Math.random(), this.vy = this.vy * Math.random())) : (this.vx = n.x + Math.random() - .5, this.vy = n.y + Math.random() - .5), this.vx_i = this.vx, this.vy_i = this.vy; 252 | var r = i.particles.shape.type; 253 | if ("object" == typeof r) { 254 | if (r instanceof Array) { 255 | var c = r[Math.floor(Math.random() * r.length)]; 256 | this.shape = c 257 | } 258 | } else this.shape = r; 259 | if ("image" == this.shape) { 260 | var o = i.particles.shape; 261 | this.img = { 262 | src: o.image.src, 263 | ratio: o.image.width / o.image.height 264 | }, this.img.ratio || (this.img.ratio = 1), "svg" == i.tmp.img_type && void 0 != i.tmp.source_svg && (i.fn.vendors.createSvgImg(this), i.tmp.pushing && (this.img.loaded = !1)) 265 | } 266 | }, i.fn.particle.prototype.draw = function () { 267 | function e() { 268 | i.canvas.ctx.drawImage(r, a.x - t, a.y - t, 2 * t, 2 * t / a.img.ratio) 269 | } 270 | var a = this; 271 | if (void 0 != a.radius_bubble) var t = a.radius_bubble; 272 | else var t = a.radius; 273 | if (void 0 != a.opacity_bubble) var s = a.opacity_bubble; 274 | else var s = a.opacity; 275 | if (a.color.rgb) var n = "rgba(" + a.color.rgb.r + "," + a.color.rgb.g + "," + a.color.rgb.b + "," + s + ")"; 276 | else var n = "hsla(" + a.color.hsl.h + "," + a.color.hsl.s + "%," + a.color.hsl.l + "%," + s + ")"; 277 | switch (i.canvas.ctx.fillStyle = n, i.canvas.ctx.beginPath(), a.shape) { 278 | case "circle": 279 | i.canvas.ctx.arc(a.x, a.y, t, 0, 2 * Math.PI, !1); 280 | break; 281 | case "edge": 282 | i.canvas.ctx.rect(a.x - t, a.y - t, 2 * t, 2 * t); 283 | break; 284 | case "triangle": 285 | i.fn.vendors.drawShape(i.canvas.ctx, a.x - t, a.y + t / 1.66, 2 * t, 3, 2); 286 | break; 287 | case "polygon": 288 | i.fn.vendors.drawShape(i.canvas.ctx, a.x - t / (i.particles.shape.polygon.nb_sides / 3.5), a.y - t / .76, 2.66 * t / (i.particles.shape.polygon.nb_sides / 3), i.particles.shape.polygon.nb_sides, 1); 289 | break; 290 | case "star": 291 | i.fn.vendors.drawShape(i.canvas.ctx, a.x - 2 * t / (i.particles.shape.polygon.nb_sides / 4), a.y - t / 1.52, 2 * t * 2.66 / (i.particles.shape.polygon.nb_sides / 3), i.particles.shape.polygon.nb_sides, 2); 292 | break; 293 | case "image": 294 | if ("svg" == i.tmp.img_type) var r = a.img.obj; 295 | else var r = i.tmp.img_obj; 296 | r && e() 297 | } 298 | i.canvas.ctx.closePath(), i.particles.shape.stroke.width > 0 && (i.canvas.ctx.strokeStyle = i.particles.shape.stroke.color, i.canvas.ctx.lineWidth = i.particles.shape.stroke.width, i.canvas.ctx.stroke()), i.canvas.ctx.fill() 299 | }, i.fn.particlesCreate = function () { 300 | for (var e = 0; e < i.particles.number.value; e++) i.particles.array.push(new i.fn.particle(i.particles.color, i.particles.opacity.value)) 301 | }, i.fn.particlesUpdate = function () { 302 | for (var e = 0; e < i.particles.array.length; e++) { 303 | var a = i.particles.array[e]; 304 | if (i.particles.move.enable) { 305 | var t = i.particles.move.speed / 2; 306 | a.x += a.vx * t, a.y += a.vy * t 307 | } 308 | if (i.particles.opacity.anim.enable && (1 == a.opacity_status ? (a.opacity >= i.particles.opacity.value && (a.opacity_status = !1), a.opacity += a.vo) : (a.opacity <= i.particles.opacity.anim.opacity_min && (a.opacity_status = !0), a.opacity -= a.vo), a.opacity < 0 && (a.opacity = 0)), i.particles.size.anim.enable && (1 == a.size_status ? (a.radius >= i.particles.size.value && (a.size_status = !1), a.radius += a.vs) : (a.radius <= i.particles.size.anim.size_min && (a.size_status = !0), a.radius -= a.vs), a.radius < 0 && (a.radius = 0)), "bounce" == i.particles.move.out_mode) var s = { 309 | x_left: a.radius, 310 | x_right: i.canvas.w, 311 | y_top: a.radius, 312 | y_bottom: i.canvas.h 313 | }; 314 | else var s = { 315 | x_left: -a.radius, 316 | x_right: i.canvas.w + a.radius, 317 | y_top: -a.radius, 318 | y_bottom: i.canvas.h + a.radius 319 | }; 320 | switch (a.x - a.radius > i.canvas.w ? (a.x = s.x_left, a.y = Math.random() * i.canvas.h) : a.x + a.radius < 0 && (a.x = s.x_right, a.y = Math.random() * i.canvas.h), a.y - a.radius > i.canvas.h ? (a.y = s.y_top, a.x = Math.random() * i.canvas.w) : a.y + a.radius < 0 && (a.y = s.y_bottom, a.x = Math.random() * i.canvas.w), i.particles.move.out_mode) { 321 | case "bounce": 322 | a.x + a.radius > i.canvas.w ? a.vx = -a.vx : a.x - a.radius < 0 && (a.vx = -a.vx), a.y + a.radius > i.canvas.h ? a.vy = -a.vy : a.y - a.radius < 0 && (a.vy = -a.vy) 323 | } 324 | if (isInArray("grab", i.interactivity.events.onhover.mode) && i.fn.modes.grabParticle(a), (isInArray("bubble", i.interactivity.events.onhover.mode) || isInArray("bubble", i.interactivity.events.onclick.mode)) && i.fn.modes.bubbleParticle(a), (isInArray("repulse", i.interactivity.events.onhover.mode) || isInArray("repulse", i.interactivity.events.onclick.mode)) && i.fn.modes.repulseParticle(a), i.particles.line_linked.enable || i.particles.move.attract.enable) 325 | for (var n = e + 1; n < i.particles.array.length; n++) { 326 | var r = i.particles.array[n]; 327 | i.particles.line_linked.enable && i.fn.interact.linkParticles(a, r), i.particles.move.attract.enable && i.fn.interact.attractParticles(a, r), i.particles.move.bounce && i.fn.interact.bounceParticles(a, r) 328 | } 329 | } 330 | }, i.fn.particlesDraw = function () { 331 | i.canvas.ctx.clearRect(0, 0, i.canvas.w, i.canvas.h), i.fn.particlesUpdate(); 332 | for (var e = 0; e < i.particles.array.length; e++) { 333 | var a = i.particles.array[e]; 334 | a.draw() 335 | } 336 | }, i.fn.particlesEmpty = function () { 337 | i.particles.array = [] 338 | }, i.fn.particlesRefresh = function () { 339 | cancelRequestAnimFrame(i.fn.checkAnimFrame), cancelRequestAnimFrame(i.fn.drawAnimFrame), i.tmp.source_svg = void 0, i.tmp.img_obj = void 0, i.tmp.count_svg = 0, i.fn.particlesEmpty(), i.fn.canvasClear(), i.fn.vendors.start() 340 | }, i.fn.interact.linkParticles = function (e, a) { 341 | var t = e.x - a.x, 342 | s = e.y - a.y, 343 | n = Math.sqrt(t * t + s * s); 344 | if (n <= i.particles.line_linked.distance) { 345 | var r = i.particles.line_linked.opacity - n / (1 / i.particles.line_linked.opacity) / i.particles.line_linked.distance; 346 | if (r > 0) { 347 | var c = i.particles.line_linked.color_rgb_line; 348 | i.canvas.ctx.strokeStyle = "rgba(" + c.r + "," + c.g + "," + c.b + "," + r + ")", i.canvas.ctx.lineWidth = i.particles.line_linked.width, i.canvas.ctx.beginPath(), i.canvas.ctx.moveTo(e.x, e.y), i.canvas.ctx.lineTo(a.x, a.y), i.canvas.ctx.stroke(), i.canvas.ctx.closePath() 349 | } 350 | } 351 | }, i.fn.interact.attractParticles = function (e, a) { 352 | var t = e.x - a.x, 353 | s = e.y - a.y, 354 | n = Math.sqrt(t * t + s * s); 355 | if (n <= i.particles.line_linked.distance) { 356 | var r = t / (1e3 * i.particles.move.attract.rotateX), 357 | c = s / (1e3 * i.particles.move.attract.rotateY); 358 | e.vx -= r, e.vy -= c, a.vx += r, a.vy += c 359 | } 360 | }, i.fn.interact.bounceParticles = function (e, a) { 361 | var t = e.x - a.x, 362 | i = e.y - a.y, 363 | s = Math.sqrt(t * t + i * i), 364 | n = e.radius + a.radius; 365 | n >= s && (e.vx = -e.vx, e.vy = -e.vy, a.vx = -a.vx, a.vy = -a.vy) 366 | }, i.fn.modes.pushParticles = function (e, a) { 367 | i.tmp.pushing = !0; 368 | for (var t = 0; e > t; t++) i.particles.array.push(new i.fn.particle(i.particles.color, i.particles.opacity.value, { 369 | x: a ? a.pos_x : Math.random() * i.canvas.w, 370 | y: a ? a.pos_y : Math.random() * i.canvas.h 371 | })), t == e - 1 && (i.particles.move.enable || i.fn.particlesDraw(), i.tmp.pushing = !1) 372 | }, i.fn.modes.removeParticles = function (e) { 373 | i.particles.array.splice(0, e), i.particles.move.enable || i.fn.particlesDraw() 374 | }, i.fn.modes.bubbleParticle = function (e) { 375 | function a() { 376 | e.opacity_bubble = e.opacity, e.radius_bubble = e.radius 377 | } 378 | 379 | function t(a, t, s, n, c) { 380 | if (a != t) 381 | if (i.tmp.bubble_duration_end) { 382 | if (void 0 != s) { 383 | var o = n - p * (n - a) / i.interactivity.modes.bubble.duration, 384 | l = a - o; 385 | d = a + l, "size" == c && (e.radius_bubble = d), "opacity" == c && (e.opacity_bubble = d) 386 | } 387 | } else if (r <= i.interactivity.modes.bubble.distance) { 388 | if (void 0 != s) var v = s; 389 | else var v = n; 390 | if (v != a) { 391 | var d = n - p * (n - a) / i.interactivity.modes.bubble.duration; 392 | "size" == c && (e.radius_bubble = d), "opacity" == c && (e.opacity_bubble = d) 393 | } 394 | } else "size" == c && (e.radius_bubble = void 0), "opacity" == c && (e.opacity_bubble = void 0) 395 | } 396 | if (i.interactivity.events.onhover.enable && isInArray("bubble", i.interactivity.events.onhover.mode)) { 397 | var s = e.x - i.interactivity.mouse.pos_x, 398 | n = e.y - i.interactivity.mouse.pos_y, 399 | r = Math.sqrt(s * s + n * n), 400 | c = 1 - r / i.interactivity.modes.bubble.distance; 401 | if (r <= i.interactivity.modes.bubble.distance) { 402 | if (c >= 0 && "mousemove" == i.interactivity.status) { 403 | if (i.interactivity.modes.bubble.size != i.particles.size.value) 404 | if (i.interactivity.modes.bubble.size > i.particles.size.value) { 405 | var o = e.radius + i.interactivity.modes.bubble.size * c; 406 | o >= 0 && (e.radius_bubble = o) 407 | } else { 408 | var l = e.radius - i.interactivity.modes.bubble.size, 409 | o = e.radius - l * c; 410 | o > 0 ? e.radius_bubble = o : e.radius_bubble = 0 411 | } if (i.interactivity.modes.bubble.opacity != i.particles.opacity.value) 412 | if (i.interactivity.modes.bubble.opacity > i.particles.opacity.value) { 413 | var v = i.interactivity.modes.bubble.opacity * c; 414 | v > e.opacity && v <= i.interactivity.modes.bubble.opacity && (e.opacity_bubble = v) 415 | } else { 416 | var v = e.opacity - (i.particles.opacity.value - i.interactivity.modes.bubble.opacity) * c; 417 | v < e.opacity && v >= i.interactivity.modes.bubble.opacity && (e.opacity_bubble = v) 418 | } 419 | } 420 | } else a(); 421 | "mouseleave" == i.interactivity.status && a() 422 | } else if (i.interactivity.events.onclick.enable && isInArray("bubble", i.interactivity.events.onclick.mode)) { 423 | if (i.tmp.bubble_clicking) { 424 | var s = e.x - i.interactivity.mouse.click_pos_x, 425 | n = e.y - i.interactivity.mouse.click_pos_y, 426 | r = Math.sqrt(s * s + n * n), 427 | p = ((new Date).getTime() - i.interactivity.mouse.click_time) / 1e3; 428 | p > i.interactivity.modes.bubble.duration && (i.tmp.bubble_duration_end = !0), p > 2 * i.interactivity.modes.bubble.duration && (i.tmp.bubble_clicking = !1, i.tmp.bubble_duration_end = !1) 429 | } 430 | i.tmp.bubble_clicking && (t(i.interactivity.modes.bubble.size, i.particles.size.value, e.radius_bubble, e.radius, "size"), t(i.interactivity.modes.bubble.opacity, i.particles.opacity.value, e.opacity_bubble, e.opacity, "opacity")) 431 | } 432 | }, i.fn.modes.repulseParticle = function (e) { 433 | function a() { 434 | var a = Math.atan2(d, p); 435 | if (e.vx = u * Math.cos(a), e.vy = u * Math.sin(a), "bounce" == i.particles.move.out_mode) { 436 | var t = { 437 | x: e.x + e.vx, 438 | y: e.y + e.vy 439 | }; 440 | t.x + e.radius > i.canvas.w ? e.vx = -e.vx : t.x - e.radius < 0 && (e.vx = -e.vx), t.y + e.radius > i.canvas.h ? e.vy = -e.vy : t.y - e.radius < 0 && (e.vy = -e.vy) 441 | } 442 | } 443 | if (i.interactivity.events.onhover.enable && isInArray("repulse", i.interactivity.events.onhover.mode) && "mousemove" == i.interactivity.status) { 444 | var t = e.x - i.interactivity.mouse.pos_x, 445 | s = e.y - i.interactivity.mouse.pos_y, 446 | n = Math.sqrt(t * t + s * s), 447 | r = { 448 | x: t / n, 449 | y: s / n 450 | }, 451 | c = i.interactivity.modes.repulse.distance, 452 | o = 100, 453 | l = clamp(1 / c * (-1 * Math.pow(n / c, 2) + 1) * c * o, 0, 50), 454 | v = { 455 | x: e.x + r.x * l, 456 | y: e.y + r.y * l 457 | }; 458 | "bounce" == i.particles.move.out_mode ? (v.x - e.radius > 0 && v.x + e.radius < i.canvas.w && (e.x = v.x), v.y - e.radius > 0 && v.y + e.radius < i.canvas.h && (e.y = v.y)) : (e.x = v.x, e.y = v.y) 459 | } else if (i.interactivity.events.onclick.enable && isInArray("repulse", i.interactivity.events.onclick.mode)) 460 | if (i.tmp.repulse_finish || (i.tmp.repulse_count++, i.tmp.repulse_count == i.particles.array.length && (i.tmp.repulse_finish = !0)), i.tmp.repulse_clicking) { 461 | var c = Math.pow(i.interactivity.modes.repulse.distance / 6, 3), 462 | p = i.interactivity.mouse.click_pos_x - e.x, 463 | d = i.interactivity.mouse.click_pos_y - e.y, 464 | m = p * p + d * d, 465 | u = -c / m * 1; 466 | c >= m && a() 467 | } else 0 == i.tmp.repulse_clicking && (e.vx = e.vx_i, e.vy = e.vy_i) 468 | }, i.fn.modes.grabParticle = function (e) { 469 | if (i.interactivity.events.onhover.enable && "mousemove" == i.interactivity.status) { 470 | var a = e.x - i.interactivity.mouse.pos_x, 471 | t = e.y - i.interactivity.mouse.pos_y, 472 | s = Math.sqrt(a * a + t * t); 473 | if (s <= i.interactivity.modes.grab.distance) { 474 | var n = i.interactivity.modes.grab.line_linked.opacity - s / (1 / i.interactivity.modes.grab.line_linked.opacity) / i.interactivity.modes.grab.distance; 475 | if (n > 0) { 476 | var r = i.particles.line_linked.color_rgb_line; 477 | i.canvas.ctx.strokeStyle = "rgba(" + r.r + "," + r.g + "," + r.b + "," + n + ")", i.canvas.ctx.lineWidth = i.particles.line_linked.width, i.canvas.ctx.beginPath(), i.canvas.ctx.moveTo(e.x, e.y), i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x, i.interactivity.mouse.pos_y), i.canvas.ctx.stroke(), i.canvas.ctx.closePath() 478 | } 479 | } 480 | } 481 | }, i.fn.vendors.eventsListeners = function () { 482 | "window" == i.interactivity.detect_on ? i.interactivity.el = window : i.interactivity.el = i.canvas.el, (i.interactivity.events.onhover.enable || i.interactivity.events.onclick.enable) && (i.interactivity.el.addEventListener("mousemove", function (e) { 483 | if (i.interactivity.el == window) var a = e.clientX, 484 | t = e.clientY; 485 | else var a = e.offsetX || e.clientX, 486 | t = e.offsetY || e.clientY; 487 | i.interactivity.mouse.pos_x = a, i.interactivity.mouse.pos_y = t, i.tmp.retina && (i.interactivity.mouse.pos_x *= i.canvas.pxratio, i.interactivity.mouse.pos_y *= i.canvas.pxratio), i.interactivity.status = "mousemove" 488 | }), i.interactivity.el.addEventListener("mouseleave", function (e) { 489 | i.interactivity.mouse.pos_x = null, i.interactivity.mouse.pos_y = null, i.interactivity.status = "mouseleave" 490 | })), i.interactivity.events.onclick.enable && i.interactivity.el.addEventListener("click", function () { 491 | if (i.interactivity.mouse.click_pos_x = i.interactivity.mouse.pos_x, i.interactivity.mouse.click_pos_y = i.interactivity.mouse.pos_y, i.interactivity.mouse.click_time = (new Date).getTime(), i.interactivity.events.onclick.enable) switch (i.interactivity.events.onclick.mode) { 492 | case "push": 493 | i.particles.move.enable ? i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb, i.interactivity.mouse) : 1 == i.interactivity.modes.push.particles_nb ? i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb, i.interactivity.mouse) : i.interactivity.modes.push.particles_nb > 1 && i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb); 494 | break; 495 | case "remove": 496 | i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb); 497 | break; 498 | case "bubble": 499 | i.tmp.bubble_clicking = !0; 500 | break; 501 | case "repulse": 502 | i.tmp.repulse_clicking = !0, i.tmp.repulse_count = 0, i.tmp.repulse_finish = !1, setTimeout(function () { 503 | i.tmp.repulse_clicking = !1 504 | }, 1e3 * i.interactivity.modes.repulse.duration) 505 | } 506 | }) 507 | }, i.fn.vendors.densityAutoParticles = function () { 508 | if (i.particles.number.density.enable) { 509 | var e = i.canvas.el.width * i.canvas.el.height / 1e3; 510 | i.tmp.retina && (e /= 2 * i.canvas.pxratio); 511 | var a = e * i.particles.number.value / i.particles.number.density.value_area, 512 | t = i.particles.array.length - a; 513 | 0 > t ? i.fn.modes.pushParticles(Math.abs(t)) : i.fn.modes.removeParticles(t) 514 | } 515 | }, i.fn.vendors.checkOverlap = function (e, a) { 516 | for (var t = 0; t < i.particles.array.length; t++) { 517 | var s = i.particles.array[t], 518 | n = e.x - s.x, 519 | r = e.y - s.y, 520 | c = Math.sqrt(n * n + r * r); 521 | c <= e.radius + s.radius && (e.x = a ? a.x : Math.random() * i.canvas.w, e.y = a ? a.y : Math.random() * i.canvas.h, i.fn.vendors.checkOverlap(e)) 522 | } 523 | }, i.fn.vendors.createSvgImg = function (e) { 524 | var a = i.tmp.source_svg, 525 | t = /#([0-9A-F]{3,6})/gi, 526 | s = a.replace(t, function (a, t, i, s) { 527 | if (e.color.rgb) var n = "rgba(" + e.color.rgb.r + "," + e.color.rgb.g + "," + e.color.rgb.b + "," + e.opacity + ")"; 528 | else var n = "hsla(" + e.color.hsl.h + "," + e.color.hsl.s + "%," + e.color.hsl.l + "%," + e.opacity + ")"; 529 | return n 530 | }), 531 | n = new Blob([s], { 532 | type: "image/svg+xml;charset=utf-8" 533 | }), 534 | r = window.URL || window.webkitURL || window, 535 | c = r.createObjectURL(n), 536 | o = new Image; 537 | o.addEventListener("load", function () { 538 | e.img.obj = o, e.img.loaded = !0, r.revokeObjectURL(c), i.tmp.count_svg++ 539 | }), o.src = c 540 | }, i.fn.vendors.destroypJS = function () { 541 | cancelAnimationFrame(i.fn.drawAnimFrame), t.remove(), pJSDom = null 542 | }, i.fn.vendors.drawShape = function (e, a, t, i, s, n) { 543 | var r = s * n, 544 | c = s / n, 545 | o = 180 * (c - 2) / c, 546 | l = Math.PI - Math.PI * o / 180; 547 | e.save(), e.beginPath(), e.translate(a, t), e.moveTo(0, 0); 548 | for (var v = 0; r > v; v++) e.lineTo(i, 0), e.translate(i, 0), e.rotate(l); 549 | e.fill(), e.restore() 550 | }, i.fn.vendors.exportImg = function () { 551 | window.open(i.canvas.el.toDataURL("image/png"), "_blank") 552 | }, i.fn.vendors.loadImg = function (e) { 553 | if (i.tmp.img_error = void 0, "" != i.particles.shape.image.src) 554 | if ("svg" == e) { 555 | var a = new XMLHttpRequest; 556 | a.open("GET", i.particles.shape.image.src), a.onreadystatechange = function (e) { 557 | 4 == a.readyState && (200 == a.status ? (i.tmp.source_svg = e.currentTarget.response, i.fn.vendors.checkBeforeDraw()) : (console.log("Error pJS - Image not found"), i.tmp.img_error = !0)) 558 | }, a.send() 559 | } else { 560 | var t = new Image; 561 | t.addEventListener("load", function () { 562 | i.tmp.img_obj = t, i.fn.vendors.checkBeforeDraw() 563 | }), t.src = i.particles.shape.image.src 564 | } 565 | else console.log("Error pJS - No image.src"), i.tmp.img_error = !0 566 | }, i.fn.vendors.draw = function () { 567 | "image" == i.particles.shape.type ? "svg" == i.tmp.img_type ? i.tmp.count_svg >= i.particles.number.value ? (i.fn.particlesDraw(), i.particles.move.enable ? i.fn.drawAnimFrame = requestAnimFrame(i.fn.vendors.draw) : cancelRequestAnimFrame(i.fn.drawAnimFrame)) : i.tmp.img_error || (i.fn.drawAnimFrame = requestAnimFrame(i.fn.vendors.draw)) : void 0 != i.tmp.img_obj ? (i.fn.particlesDraw(), i.particles.move.enable ? i.fn.drawAnimFrame = requestAnimFrame(i.fn.vendors.draw) : cancelRequestAnimFrame(i.fn.drawAnimFrame)) : i.tmp.img_error || (i.fn.drawAnimFrame = requestAnimFrame(i.fn.vendors.draw)) : (i.fn.particlesDraw(), i.particles.move.enable ? i.fn.drawAnimFrame = requestAnimFrame(i.fn.vendors.draw) : cancelRequestAnimFrame(i.fn.drawAnimFrame)) 568 | }, i.fn.vendors.checkBeforeDraw = function () { 569 | "image" == i.particles.shape.type ? "svg" == i.tmp.img_type && void 0 == i.tmp.source_svg ? i.tmp.checkAnimFrame = requestAnimFrame(check) : (cancelRequestAnimFrame(i.tmp.checkAnimFrame), i.tmp.img_error || (i.fn.vendors.init(), i.fn.vendors.draw())) : (i.fn.vendors.init(), i.fn.vendors.draw()) 570 | }, i.fn.vendors.init = function () { 571 | i.fn.retinaInit(), i.fn.canvasInit(), i.fn.canvasSize(), i.fn.canvasPaint(), i.fn.particlesCreate(), i.fn.vendors.densityAutoParticles(), i.particles.line_linked.color_rgb_line = hexToRgb(i.particles.line_linked.color) 572 | }, i.fn.vendors.start = function () { 573 | isInArray("image", i.particles.shape.type) ? (i.tmp.img_type = i.particles.shape.image.src.substr(i.particles.shape.image.src.length - 3), i.fn.vendors.loadImg(i.tmp.img_type)) : i.fn.vendors.checkBeforeDraw() 574 | }, i.fn.vendors.eventsListeners(), i.fn.vendors.start() 575 | }; 576 | Object.deepExtend = function (e, a) { 577 | for (var t in a) a[t] && a[t].constructor && a[t].constructor === Object ? (e[t] = e[t] || {}, arguments.callee(e[t], a[t])) : e[t] = a[t]; 578 | return e 579 | }, window.requestAnimFrame = function () { 580 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (e) { 581 | window.setTimeout(e, 1e3 / 60) 582 | } 583 | }(), window.cancelRequestAnimFrame = function () { 584 | return window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || clearTimeout 585 | }(), window.pJSDom = [], window.particlesJS = function (e, a) { 586 | "string" != typeof e && (a = e, e = "particles-js"), e || (e = "particles-js"); 587 | var t = document.getElementById(e), 588 | i = "particles-js-canvas-el", 589 | s = t.getElementsByClassName(i); 590 | if (s.length) 591 | for (; s.length > 0;) t.removeChild(s[0]); 592 | var n = document.createElement("canvas"); 593 | n.className = i, n.style.width = "100%", n.style.height = "100%"; 594 | var r = document.getElementById(e).appendChild(n); 595 | null != r && pJSDom.push(new pJS(e, a)) 596 | }, window.particlesJS.load = function (e, a, t) { 597 | var i = new XMLHttpRequest; 598 | i.open("GET", a), i.onreadystatechange = function (a) { 599 | if (4 == i.readyState) 600 | if (200 == i.status) { 601 | var s = JSON.parse(a.currentTarget.response); 602 | window.particlesJS(e, s), t && t() 603 | } else console.log("Error pJS - XMLHttpRequest status: " + i.status), console.log("Error pJS - File config not found") 604 | }, i.send() 605 | }; --------------------------------------------------------------------------------