├── .gitignore ├── .DS_Store ├── requirements.txt ├── README.md ├── LICENSE └── rsp_scrn.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.gif 2 | *.log 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sesh/rsp_scrn/master/.DS_Store -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.6 2 | imageio==1.5 3 | numpy==1.11.0 4 | Pillow==3.2.0 5 | PyTweening==1.0.3 6 | selenium==2.53.2 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Screenshot Responsive Sites 2 | 3 | A simple Python script that uses Selenium, PhantomJS and Pillow to create animated GIFs of how websites respond to 4 | different browser widths. Useful for testing your site's responsive breakpoints and comparing it to others. 5 | 6 | 7 | ### Usage 8 | 9 | ``` 10 | python rsp_scrn.py [--resize] [--sleep=] 11 | 12 | --resize Shrink the final GIF to save on filesize 13 | --sleep Wait x seconds before taking the screenshot 14 | ``` 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Brenton Cleeland 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /rsp_scrn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import re 5 | import sys 6 | import time 7 | from datetime import date 8 | from io import BytesIO 9 | 10 | import click 11 | import imageio 12 | import numpy 13 | import pytweening 14 | from PIL import Image 15 | from selenium import webdriver 16 | 17 | 18 | 19 | def get_iphone_browser(): 20 | dcap = dict(webdriver.DesiredCapabilities.PHANTOMJS) 21 | dcap["phantomjs.page.settings.userAgent"] = 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D201 Safari/9537.53' # noqa 22 | browser = webdriver.PhantomJS(desired_capabilities=dcap) 23 | return browser 24 | 25 | 26 | def valid_filename(value): 27 | value = value.strip().replace(' ', '_') 28 | return re.sub(r'(?u)[^-\w.]', '', value) 29 | 30 | 31 | def take_screenshot(url, width, height, max_size, sleep=0, resize=False): 32 | print('[{}x{}] URL: {}'.format(width, height, url)) 33 | 34 | if width < 450: 35 | browser = get_iphone_browser() 36 | else: 37 | browser = webdriver.PhantomJS() 38 | 39 | browser.set_window_size(width, height) 40 | browser.get(url) 41 | 42 | if sleep: 43 | print('Taking a nap, see you in {} seconds'.format(sleep)) 44 | time.sleep(sleep) 45 | 46 | screenshot_from_selenium = BytesIO(browser.get_screenshot_as_png()) 47 | browser.quit() 48 | 49 | screenshot_canvas = Image.new('RGB', max_size, (222, 222, 222)) 50 | screenshot_no_bg = Image.open(screenshot_from_selenium) 51 | 52 | screenshot = Image.new("RGB", screenshot_no_bg.size, (255, 255, 255)) 53 | screenshot.paste(screenshot_no_bg, mask=screenshot_no_bg.split()[3]) 54 | 55 | # retina display + chrome ends up with screenshots that are too large, resize those 56 | if screenshot.width > width: 57 | screenshot = screenshot.resize((width, int(screenshot.height * (width / screenshot.width)))) 58 | 59 | screenshot_canvas.paste(screenshot, (0, 0)) 60 | 61 | if resize: 62 | screenshot_canvas = screenshot_canvas.resize((960, 600)) 63 | 64 | return numpy.asarray(screenshot_canvas) 65 | 66 | 67 | @click.command() 68 | @click.argument('url') 69 | @click.option('--sleep', default=0) 70 | @click.option('--resize', is_flag=True) 71 | def responsive_screenshot(url, sleep, resize): 72 | if not url.startswith('http://') and not url.startswith('https://'): 73 | sys.exit('URL must start with http:// or https://') 74 | 75 | filename = valid_filename(url.split('://')[-1]) 76 | # note: PhantomJS always returns a screenshot that's the full height of the page 77 | # note: Chromium doesn't resize smaller than ~500x500 78 | largest_size = (1920, 1200) 79 | smallest_size = (320, 1200) # iPhone 4 80 | 81 | frames = [] 82 | for x in range(0, 101, 5): # +1 to ensure that we include the largest size in our set 83 | width, height = pytweening.getPointOnLine(*smallest_size, *largest_size, x / 100.0) 84 | frame = take_screenshot(url, int(width), int(height), largest_size, int(sleep), resize) 85 | frames.append(frame) 86 | imageio.mimwrite('{}-{}.gif'.format(str(date.today()), filename), frames, duration=0.2) 87 | 88 | 89 | if __name__ == '__main__': 90 | responsive_screenshot() 91 | --------------------------------------------------------------------------------